C++ transform parameter initialization question - c++

I was trying to transform a string into lowercase and store it in another variable using std::transform and std::tolower. I first tried:
string str1("Hello");
string lowerStr1;
transform(str1.begin(), str1.end(), lowerStr1.begin(), ::tolower);
cout << lowerStr1 << endl;
But, lowerStr1 contained nothing. After initializing lowerStr1 with str1, I got the desired result. I want to know the intuition behind this. Could someone explain why lowerStr1 should be initialized in this case?

lowerStr1 is empty, and std::transform won't insert elements into it.
std::transform applies the given function to a range and stores the result in another range, beginning at d_first.
You can use std::back_inserter, which constructs a std::back_insert_iterator, which would call push_back() on the container to insert elements.
transform(str1.begin(), str1.end(), back_inserter(lowerStr1), ::tolower);
Or make lowerStr1 containing 5 elements in advance.
string lowerStr1(5, '\0');
transform(str1.begin(), str1.end(), lowerStr1.begin(), ::tolower);
or
string lowerStr1;
lowerStr1.resize(5);
transform(str1.begin(), str1.end(), lowerStr1.begin(), ::tolower);
Could someone explain why lowerStr1 should be initialized in this case?
That's because you initialize lowerStr1 containing 5 elements in advance as above. What's the value of the initialized elements doens't matter in fact.

This is because your call to std::transform is logically equivalent to the following code:
auto b=str1.begin();
auto e=str1.end();
auto p=lowerStr1.begin();
while (b != e)
{
*p=tolower(*b);
++b;
++e;
}
But lowerStr1, is a completely empty string. lowerStr1.begin() gives you, loosely speaking, a pointer to an empty string. So writing to that pointer and, adding insult to injury, incrementing it and continuing to write to it, result in undefined behavior, memory corruption, and a non-trivial possibility of a crash.
You do not add content to an empty string by grabbing a pointer to it, and then scribbling into that pointer. There are several ways of doing that correctly, with push_back() or insert() methods. You can also use an iterator that does that, like a std::back_insert_iterator, which can use with std::transform.

Generic algorithms won't change the size of the containers.
You need to use an iterator adapter which implements operator= in a special way so that it actually insert elements.
Therefore you can use back_inserter(lowerStr1) to make sure that lowerStr1 gets extended as trasform() does assignments.
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string str1("Hello");
string lowerStr1;
transform(str1.begin(), str1.end(), std::back_inserter(lowerStr1), ::tolower);
cout << lowerStr1 << endl;
}

Related

How to search and delete characters in string

I'm trying to convert .fsp files to strings but new .fsp file is very abnormal. It contains some undesirable characters that I want to delete from string. How can I make it?
I have tried to search char in string and delete them but I dont know how to make it.
The string looks like this:
string s;
s = 144˙037˙412˙864;
and I need to make it just like that
s = 144037412864;
So I except result like this:
string s = 144037412864;
Thank you for help.
We can use the remove-erase idiom to remove unnecessary characters from the string! There's a function in <algorithm> called remove_if. What remove_if does is it removes elements that match some predicate. remove_if returns a iterator pointing to the new end of the container after all elements have been removed. I'll show you how to write a function that does the job!
#include <algorithm>
#include <string>
void erase_ticks(std::string& s) {
// Returns true for characters that should be removed
auto condition = [](char c) { return c == '`'; };
// Removes characters that match the condition,
// and returns the new endpoint of the string
auto new_end = std::remove_if(s.begin(), s.end(), condition);
// Erases characters from the new endpoint to the current endpoint
s.erase(new_end, s.end());
}
We can use this in main, and it works just as expected!
#include <iostream>
int main() {
std::string s("123`456`789");
std::cout << s << '\n'; // prints 123`456`789
erase_ticks(s);
std::cout << s << '\n'; // prints 123456789
}
This problem has two parts, first we need to identify any characters in the string which we don't want. From your use case it seems that anything that is not numeric needs to go. This is simple enough as the standard library defines a function std::isdigit (simply add the following inclusion "#include <locale>") which takes a character and returns a bool which indicates whether or not the character is numeric.
Second we need a way to quickly and cleanly remove all occurrences of these from the string. Thus we can use the 'Erase Remove' idiom to iterate through the string and do what we want.
string s = "123'4'5";
s.erase(std::remove_if(s.begin(), s.end(), [](char x)->bool {return !std::isdigit(x);}), s.end());
In the snippit above we're calling erase on the string which takes two iterators, the first refers to where we want to begin to delete from and the second tells the call where we want to delete to. The magic in this trick is actually all in the call to remove_if (include "#include <algorithm>" for it). remove_if actually works by shifting the elements (or characters) of string forward to the end of the string.
So "123'4'5'" becomes "12345'''", then it returns an iterator to where it shifted these characters to which is then passed to erase to tell it remove the characters starting here. In the end we're left with "12345" as expected.
Edit: Forgot to mention, remove_if also takes a predicate here I'm using a lambda which takes a character and returns a bool.

For the erase-remove idiom, why is the second parameter necessary which points to the end of the container?

Consider the following code (taken from cppreference.com, slightly adapted):
#include <algorithm>
#include <string>
#include <iostream>
#include <cctype>
int main()
{
std::string str1 = " Text with some spaces";
str1.erase(std::remove(str1.begin(), str1.end(), ' '), str1.end());
std::cout << str1 << '\n';
return 0;
}
Why is the second parameter to erase neccessary? (I.e. str1.end() in this case.)
Why can't I just supply the iterators which are returned by remove to erase? Why do I have to tell it also about the last element of the container from which to erase?
The pitfall here is that you can also call erase without the second parameter but that produces the wrong result, obviously.
Are there use cases where I would not want to pass the end of the container as a second parameter to erase?
Is omitting the second parameter of erase for the erase-remove idiom always an error or could that be a valid thing to do?
std::remove returns one iterator; it's the new past-the-end iterator for the sequence. But when the sequence is managed by a container, the size of the container hasn't changed; std::remove shuffles the order of the elements in the sequence, but doesn't actually remove any of them.
To get rid of the elements in the container that are not part of the new sequence you call, of course, container.erase(). But the goal is to remove all the extra elements; calling container.erase() with only one iterator tells it to remove that element. To tell container.erase() to erase everything "from here to the end" you have to tell it both where "here" is and where the end is. So that means two iterators.
If it helps, think of the "remove/erase" idiom as two separate steps:
auto new_end = std::remove(str1.begin(), str1.end(), ' ');
str1.erase(new_end, str1.end());

Is it possible to iterate over an iterator?

I have a working program that capitalizes strings in a vector, using iterators:
vector<string> v7{ 10, "apples" };
for (auto vIterator= v7.begin(); vIterator!= v7.end(); ++vIterator){
auto word = *vIterator; //here
auto charIterator = word.begin();
*charIterator = toupper(*charIterator);
*vIterator = word; //also here, i guess i could just print `word` instead?
cout << *vIterator << endl;
}
My question is;
2nd line inside the loop # the comment, i had to save the pointer to the iterator to another string variable before i was able to iterate over it.
Iterating over the pointer like so
*vIterator.begin();
didn't seem to work.
Is this the correct practice, or am i missing something?
I'm new to the C languages, the concept behind pointer-like tools is quite hard to understand even if i can use them, and in this case it just feels like I'm doing it wrong.
Edit: It was a syntax error (*vIterator).begin();
It just didn't make sense why i'd have to save it to another variable before iterating over it, cheers.
Since you are using C++11 look how simpler your code can become using ranged loops like the example below:
std::vector<std::string> v(10, "apples");
for(auto &&word : v) {
word[0] = toupper(word[0]);
}
LIVE DEMO
Now as far as it concerns the (*vIterator.begin(); didn't seem to work.):
The dot operator (i.e., .) has a higher precedence than the dereference operator (i.e., *). Thus, *vIterator.begin() is interpreted as *(vIterator.begin()). The compiler rightfully complains because vIterator hasn't got a member begin().
Think of iterators as if they were pointers. The correct way to access the members of an object via a pointer/iterator pointing to it is either using the arrow operator (i.e., vIterator->begin()) or first dereference the pointer/iterator and then use the dot operator (i.e., (*vIterator).begin()).
So your code via the use of iterators would become:
std::vector<std::string> v(10, "apples");
for(auto it(v.begin()), ite(v.end()); it != ite; ++it) {
*(it->begin()) = toupper(*(it->begin()));
}
LIVE DEMO
The correct way to write *vIterator.begin(); is (*vIterator).begin(); or, more often, vIterator->begin();. Also note that you can also access the first character of a string directly (without having to iterate over it) as word[0].
A simple STL-ish way of doing it:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<string> v7{ 10, "apples" };
for_each(v7.begin(), v7.end(), [](string& word){word[0] = toupper(word[0]);});
}

Replacing elements in vector using erase and insert

void replace(vector<string> my_vector_2, string old, string replacement){
vector<string>::iterator it;
for (it = my_vector_2.begin(); it != my_vector_2.end(); ++it){
if (*it==old){
my_vector_2.erase(it);
my_vector_2.insert(it,replacement);
}
}
}
So, I'd like this function to replace all occurrences of the string old in the vector with the string replacement. But when calling this function, it simply doesn't change the vector at all. I'm not sure if I am using the erase and insert functions properly. Any ideas?
At first you need to pass vector by reference, not by value.
void replace(vector<string>& my_vector_2, string old, string replacement){
Second erase and insert invalidates it, you need to update it with new iterator returned by erase
it = my_vector_2.erase(it);
it = my_vector_2.insert(it,replacement);
There's an ready-made algorithm for your problem:
#include <algorithm>
#include <string>
#include <vector>
std::vector<std::string> v; // populate
std::replace(v.begin(), v.end(), "old", "new");
You are passing your std::vector as a value. In order to Change the std::vector you pass to the function, declare it as a reference
void replace(vector<string>& my_vector_2, string old, string replacement){ }
The & denotes that you pass your std::vector by reference and so you can access the object you passed.
And don't erase the element, simply replace it.

C++ std::transform() and toupper() ..why does this fail?

I have 2 std::string. I just want to, given the input string:
capitalize every letter
assign the capitalized letter to the output string.
How come this works:
std::string s="hello";
std::string out;
std::transform(s.begin(), s.end(), std::back_inserter(out), std::toupper);
but this doesn't (results in a program crash)?
std::string s="hello";
std::string out;
std::transform(s.begin(), s.end(), out.begin(), std::toupper);
because this works (at least on the same string:
std::string s="hello";
std::string out;
std::transform(s.begin(), s.end(), s.begin(), std::toupper);
There is no space in out. C++ algorithms do not grow their target containers automatically. You must either make the space yourself, or use a inserter adaptor.
To make space in out, do this:
out.resize(s.length());
[edit] Another option is to create the output string with correct size with this constructor.
std::string out(s.length(), 'X');
I'd say that the iterator returned by out.begin() is not valid after a couple of increments for the empty string. After the first ++ it's ==out.end(), then the behavior after the next increment is undefined.
After all this exactly what insert iterator is for.
Thats the sense of a backinserter: It inserts elements to a container. using begin(), you pass a iterator to a empty container and modify invalid iterators.
I am sorry - my edits interfered with your comments. I first posted something wrong accidentally.