C++ STL: Trouble with iterators - c++

I'm having a beginner problem:
bool _isPalindrome(const string& str)
{
return _isPalindrome(str.begin(), str.end()); // won't compile
}
bool _isPalindrome(string::iterator begin, string::iterator end)
{
return begin == end || *begin == *end && _isPalindrome(++begin, --end);
}
What am I doing wrong here? Why doesn't str.begin() get type checked to be a string::iterator?
Update: Better version:
bool BrittlePalindrome::_isPalindrome(string::const_iterator begin, string::const_iterator end)
{
return begin >= end || *begin == *(end - 1) && _isPalindrome(++begin, --end);
}

Assuming that you have a declaration of the second function before the first function, the main issue is that you are passing the strings by const reference.
This means that the only overloads of begin() and end() that you have access to are the const versions which return std::string::const_iterator and not std::string::iterator.
The convention for iterators is that the end iterator points one beyond the end of a range and is not dereferencable - certainly if you pass str.end() as the end parameter. This means that *begin == *end is not valid, you need to decrement end once first. You are also going to have an issue with ranges with odd numbers of elements. By doing ++begin and --end with no further checking your iterators may cross over in the recursion rather than triggering the begin == end condition.
Also note that for maximum portability, global identifiers shouldn't start with an underscore.

str.begin() is non-const, while the argument str is const.
You can either change the iterator-accepting method to accept const_iterators, or you can change the string-accepting method to accept a non-const string.
Or you could cast away str's const-ness, but that would be a patent Bad Idea TM.
(I would also parenthesize your return statement on the iterator-accepting method to make your intent more clear, but that's neither here nor there.)

As previously mentioned your iterators need to be constant iterators, but there's something else wrong with your algorithm. It works fine if you have a string of odd length, but do you see what happens when your string is even length? Consider the palindrome:
aa
Your algorithm will pass in an iterator pointing to the front and to the end. All's good, then it will go to the next level, and all will still be good, but it won't end. Because your first condition will never be true. You need to check not only if begin==end but if begin+1==end or begin==end-1 if you prefer. Otherwise you're iterators are going to be upset.

What error are you getting?
Have you tried this?
bool _isPalindrome(string::const_iterator begin, string::const_iterator end)

replace iterator by const_iterator
swap function definitions
decrement end
Code:
bool isPalindrome(string::const_iterator begin, string::const_iterator end)
{
return (begin == end || begin == --end ||
*begin == *end && isPalindrome(++begin, end));
}
bool isPalindrome(const string& str)
{
return isPalindrome(str.begin(), str.end());
}

You haven't declared the second function before calling it in the first function. The compiler can't find it and thus tries to convert str.begin() (string::iterator) into a const string &. You can move the first function behind the second function.

Related

How to adapt a string splitting algorithm using pointers so it uses iterators instead?

The code below comes from an answer to this question on string splitting. It uses pointers, and a comment on that answer suggested it could be adapted for std::string. How can I use the features of std::string to implement the same algorithm, for example using iterators?
#include <vector>
#include <string>
using namespace std;
vector<string> split(const char *str, char c = ',')
{
vector<string> result;
do
{
const char *begin = str;
while(*str != c && *str)
str++;
result.push_back(string(begin, str));
} while (0 != *str++);
return result;
}
Ok so I obviously replaced char by string but then I noticed he is using a pointer to the beginning of the character. Is that even possible for strings? How do the loop termination criteria change? Is there anything else I need to worry about when making this change?
You can use iterators instead of pointers. Iterators provide a way to traverse containers, and can usually be thought of as analogous to pointers.
In this case, you can use the begin() member function (or cbegin() if you don't need to modify the elements) of a std::string object to obtain an iterator that references the first character, and the end() (or cend()) member function to obtain an iterator for "one-past-the-end".
For the inner loop, your termination criterion is the same; you want to stop when you hit the delimiter on which you'll be splitting the string. For the outer loop, instead of comparing the character value against '\0', you can compare the iterator against the end iterator you already obtained from the end() member function. The rest of the algorithm is pretty similar; iterators work like pointers in terms of dereference and increment:
std::vector<std::string> split(const std::string& str, const char delim = ',') {
std::vector<std::string> result;
auto end = str.cend();
auto iter = str.cbegin();
while (iter != end) {
auto begin = iter;
while (iter != end && *iter != delim) ++iter;
result.push_back(std::string(begin, iter));
if (iter != end) ++iter; // See note (**) below.
}
return result;
}
Note the subtle difference in the inner loop condition: it now tests whether we've hit the end before trying to dereference. This is because we can't dereference an iterator that points to the end of a container, so we must check this before trying to dereference. The original algorithm assumes that a null character ends the string, so we're ok to dereference a pointer to that position.
(**) The validity of iter++ != end when iter is already end is under discussion in Are end+1 iterators for std::string allowed?
I've added this if statement to the original algorithm to break the loop when iter reaches end in the inner loop. This avoids adding one to an iterator which is already the end iterator, and avoids the potential problem.

Can I remove elements in the std::string object while iterating over it

Can I remove elements in the std::string object while iterating over it?
for (auto itr = str.rbegin(), rend = str.rend(); itr != str.rend() && *itr == '0'; ++itr)
{
str.pop_back();
}
No, that's not allowed. Modifying the contents of the string invalidates any iterators, particularly itr.
To remove trailing characters from a string, consider using find_last_not_of:
auto ix = str.find_last_not_of('0');
str.resize(ix + 1);
Another option is to use the erase function, which will return the next iterator in the sequence, thereby avoiding having any invalid iterators.
for (auto itr = str.rbegin(); itr != str.rend() && *itr == '0'; /*nothing*/)
itr = str.erase(itr);
That will erase the last character (as pop_back would) and safely advance the iterator, so you never actually have an invalid iterator anymore. The caveat is that you cannot you the rend iterator that you were calculating before, because it would be invalid; however, you weren't actually using it anyway.
You can remove elements as you iterate over the string, but you need to write your loop slightly differently to do it safely. In this case, we only really care about the last character in the string, so we can do something like this:
for (auto itr = str.rbegin();
itr != str.rend() && *itr == '0';
itr=str.rbegin())
{
str.pop_back();
}
Even though itr may be invalidated when we do pop_back on the string, we're re-fetching the value of str.rbegin() every iteration, and that's guaranteed to give a valid reverse iterator every time we call it. What we have left no longer really makes good use of a for loop though--we might about as well use a while loop:
while (str.rbegin() != str.rend() && *str.rbegin() == '0')
str.pop_back();
I think I'd rather write it something like:
while (!str.empty() && *str.rbegin() == '0')
str.pop_back();
...or (using a slightly cleaner equivalent of *str.rbegin():
while (!str.empty() && str.back() == '0')
str.pop_back();
According to reference of string::pop_back http://www.cplusplus.com/reference/string/string/pop_back/
Any iterators, pointers and references related to this object may be
invalidated.
So I guess you cannot do this in a for loop with iterators.
You do this:
while ( str.size() > 0 && str[ str.size()-1] == '0' ] )
str.pop_back();
Or you can carry out your for loop using counters instead of iterators.
According to the Scripture, Chapter 21.4.1, verse 6:
6 References, pointers, and iterators referring to the elements of a
basic_string sequence may be invalidated by the following uses of that
basic_string object:
— as an argument to any standard library function
taking a reference to non-const basic_string as an argument.
— Calling non-const member functions, except operator[], at, front, back, begin,
rbegin, end, and rend.
So I would say "no, not using pop_back".
But you can of course use the erase overload that returns an iterator and use that iterator instead of the one you erased, as for other containers.
According to the standard any use of s.erase() on a std::basic_string invalidates all pointers, references, and iterators to s. The relevant section in the standard is 21.4.1 [string.require] paragraph 6:
References, pointers, and iterators referring to the elements of a basic_string sequence may be invalidated by the following uses of that basic_string object:
as an argument to anys tandard library function taking a reference to non-const basic_string as`an argument.
Calling non-const member functions, except operator[], at, front, back, begin, rbegin, end, and rend.
The alterantive is to use the result or the erase() to get back a valid iterator to the current position. Alternatively, you might want to use erase() in combination with std::remove_if() to first reshuffle the string efficiently and then remove the actual content.

How to use the isspace function in c++?

I'm trying to figure out how to use this function. I found it on the web and apparently it checks if you have a space in your string. So it's not working out for me. I've figured out that I'm not even getting into the if statement that I need to.
for (i=0;i < marks.length();i++)
{
if (isdigit(marks[i]))
{
floatMARK = 1;
}
else
{
charMARK = 1;
}
}
if (floatMARK == 1)
{
printf("were in.");
for (i=0;i < marks.length();i++)
{
if (isspace(marks[i]))
{
multiMARK = 1;
printf("WE HAVE A SPACE!!");
}
}
}
Anyone know what I'm doing wrong? If you need me to clarify anything, let me know.
All that is very unnecessary to just test if a string has a space in it. This is all you need:
#include <ctype.h>
bool hasspace = std::find_if(str.begin(), str.end(), ::isspace) != str.end();
:: is the scope resolution operator specifying that isspace is a global function, not the similarly-named std::isspace, and find_if is a function inside std::. If you use using namespace std; then you don't need std:: but you do still need the plain ::.
The find_if function takes an iterator to the beginning of the string, an iterator to the end of the string, and a function that takes an argument and returns some value convertible to a bool. find_if iterates from the first iterator to the second iterator, passing each value of the current item to the function you gave it, and if the function returns true, find_if returns the iterator that caused the function to return true. If find_if goes through to the end and the function never returns true, then it returns an iterator to the end of the range, which in this case is str.end().
That means that if find_if returns str.end(), it got to the end of the string without isspace returning true, which means there was no space characters in the string. Therefore, you can test the result of find_if against str.end(); If they are unequal (!=), that means there was a space in the string, and hasspace is true. Else, hasspace is false.
here is another way, if the above version seems strange, or it's above your knowledge
if(marks[i] == ' ') {
cout<<"Space found!";
}

Replace multiple spaces with one space in a string

How would I do something in c++ similar to the following code:
//Lang: Java
string.replaceAll(" ", " ");
This code-snippet would replace all multiple spaces in a string with a single space.
bool BothAreSpaces(char lhs, char rhs) { return (lhs == rhs) && (lhs == ' '); }
std::string::iterator new_end = std::unique(str.begin(), str.end(), BothAreSpaces);
str.erase(new_end, str.end());
How this works. The std::unique has two forms. The first form goes through a range and removes adjacent duplicates. So the string "abbaaabbbb" becomes "abab". The second form, which I used, takes a predicate which should take two elements and return true if they should be considered duplicates. The function I wrote, BothAreSpaces, serves this purpose. It determines exactly what it's name implies, that both of it's parameters are spaces. So when combined with std::unique, duplicate adjacent spaces are removed.
Just like std::remove and remove_if, std::unique doesn't actually make the container smaller, it just moves elements at the end closer to the beginning. It returns an iterator to the new end of range so you can use that to call the erase function, which is a member function of the string class.
Breaking it down, the erase function takes two parameters, a begin and an end iterator for a range to erase. For it's first parameter I'm passing the return value of std::unique, because that's where I want to start erasing. For it's second parameter, I am passing the string's end iterator.
So, I tried a way with std::remove_if & lambda expressions - though it seems still in my eyes easier to follow than above code, it doesn't have that "wow neat, didn't realize you could do that" thing to it.. Anyways I still post it, if only for learning purposes:
bool prev(false);
char rem(' ');
auto iter = std::remove_if(str.begin(), str.end(), [&] (char c) -> bool {
if (c == rem && prev) {
return true;
}
prev = (c == rem);
return false;
});
in.erase(iter, in.end());
EDIT realized that std::remove_if returns an iterator which can be used.. removed unnecessary code.
A variant of Benjamin Lindley's answer that uses a lambda expression to make things cleaner:
std::string::iterator new_end =
std::unique(str.begin(), str.end(),
[=](char lhs, char rhs){ return (lhs == rhs) && (lhs == ' '); }
);
str.erase(new_end, str.end());
Why not use a regular expression:
boost::regex_replace(str, boost::regex("[' ']{2,}"), " ");
how about isspace(lhs) && isspace(rhs) to handle all types of whitespace

C++ removing punctuation on strings, erase()/iterator issue

I know I'm not the first person to bring up the issue with reverse iterators trying to call the erase() method on strings. However, I wasn't able to find any good ways around this.
I'm reading the contents of a file, which contains a bunch of words. When I read in a word, I want to pass it to a function I have called stripPunct. However, I ONLY want to strip punctuation at the beginning and end of a string, not in the middle.
So for instance:
(word) should strip '(' and ')' resulting in just word
don't! should strip '!' resulting in just don't
So my logic (which I'm sure could be improved) was to have two while loops, one starting at the end and one at the beginning, traversing and erasing until it hits a non-punctuation char.
void stripPunct(string & str) {
string::iterator itr1 = str.begin();
string::reverse_iterator itr2 = str.rbegin();
while ( ispunct(*itr1) ) {
str.erase(itr1);
itr1++;
}
while ( ispunct(*itr2) ) {
str.erase(itr2);
itr2--;
}
}
However, obviously it's not working because erase() requires a regular iterator and not a reverse_iterator. But anyways, I feel like that logic is pretty inefficient.
Also, I tried instead of a reverse_iterator using just a regular iterator, starting it at str.end(), then decremented it, but it says I cannot dereference the iterator if I start it at str.end().
Can anyone help me with a good way to do this? Or maybe point out a workaround for what I already have?
Thank you so much in advance!
------------------ [ EDIT ] ----------------------------
found a solution, although it may not be the best solution:
// Call the stripPunct method:
stripPunct(str);
if ( !str.empty() ) { // make sure string is still valid
// perform other code
}
And here is the stripPunct method:
void stripPunct(string & str) {
string::iterator itr1 = str.begin();
string::iterator itr2 = str.end();
while ( !(str.empty()) && ispunct(*itr1) )
itr1 = str.erase(itr1);
itr2--;
if ( itr2 != str.begin() ) {
while ( !(str.empty()) && ispunct(*itr2) ) {
itr2 = str.erase(itr2);
itr2--;
}
}
}
First, note a couple problems with your code:
after you call erase() using itr1, you've invalidated itr2.
when using a reverse_iterator to go backwards through a sequence, you want to use ++, not -- (that's kind of the reason reverse iterators exist).
Now, to improve the logic, you can avoid erasing each character individually by finding the first charater you don't want to erase and erase everything up to that point. find_if() can be used to help with that:
int not_punct(char c) {
return !ispunct((unsigned char) c);
}
void stripPunct(string & str) {
string::iterator itr = find_if( str.begin(), str.end(), not_punct);
str.erase( str.begin(), itr);
string::reverse_iterator ritr = find_if( str.rbegin(), str.rend(), not_punct);
str.erase( ritr.base(), str.end());
}
Note that I've used base() to get the 'regular' iterator corresponding to the reverse_iterator. I find the logic for whether base() needs to be adjusted confusing (reverse iterators in general confuse me)- in this case it doesn't because we happen to want to start the erase after the character that's found.
This article by Scott Meyers, http://drdobbs.com/cpp/184401406, has a good treatment of reverse_iterator::base() in the section. "Guideline 3: Understand How to Use a reverse_iterator's Base iterator". The information in that article has also been incorporated into Meyer's "Effective STL" book.
You can't dereference iterator::end() because it points to invalid memory (memory right after the end of the array), so you have to decrement it first.
And one final note: if the word consists only of punctuations, your program will fail, be sure to handle that.
If you don't mind negative logic, you can do the following:
string tmp_str="";
tmp_str.reserve(str.length());
for (string::iterator itr1 = str.begin(); itr1 != str.end(); itr1++)
{
if (!ispunct(*itr1))
{
tmp_str.push_back(*itr1);
}
}
str = tmp_str;