member function erase() not working in a loop - c++

I'm programming a little game; but stringname.erase() seems to be not working in a 'for-loop' , I want to understand why, I have other alternatives, but I don't understand what's going on in the following code.
More explications of my situation (Important!):
guess is a char.
'tmgword' and 'word' are of type string, and: tmgword = word ;
what I understand from my code:
in the first time,the 'while'-loop verifies if there is 'guess' in the string 'tmpgword'.
That is true and the for-loop is working fine, the right character(guess) that verifies the if-condition is erased.
in the second time: the 'while'-loop verifies again if there is 'guess' in the string 'tmpgword'.
that is true, and hence we go into the 'for-loop' again; and then into the 'if'-block ( the right char is found ) but here erase() don't work, and we enter in an infinite loop.
when the program finds the right index using 'for-loop', I break, and I start the search from the beginning in case there are more occurrences of guess.
the problem is: the program finds 'guess' again but erase() won't delete it!
can someone explain please. Here is my code:
while (tmpgword.find(guess,0) != string::npos )
{
for (i = 0; i < word.size(); i++) // verify the input;
{
if (word[i] == guess)
{
encword[i] = word[i];//I don't think this line is important
tmpgword.erase(tmpgword.begin() + i);
break;
}
}
}

After you do the first erase, the character positions in tmpgword are not the same as in word.
string::find() returns the position of the element when it's found, so you can use that instead of looping through word.
size_t pos = 0;
while ((pos = tmpgword.find(guess, pos)) != string::npos) {
tmpgword.erase(pos, 1);
}
I've used pos as the starting position for each call to find() so it starts from where it just erased, rather than searching from the beginning each time through (there can't be any occurrences before that, because they've all been erased).

Related

Add character at every vowel

I had a similiar question earlier and managed to solve it. Now I'm trying to do it backwards, this is my code at the moment.
Basically I want the end result to "p5A5SSW5o5R5o5d", but when I run this it just find the first "o" which is in the first part of passworod, I need it to skip the the vowel it have already added a 5 before and after to.
I want every vowel (only included AOao in the current string as thats all the vowels appearing in my string), to have a 5 as prefix and suffix. It gets stuck at the first o and doesnt proceed to the next o. I have created a nested for loop which means it takes the first character in the encrypt-string, proceeds to the next for-loop and loops through every single vowel Ive included in the vowel string until it finds a match. Otherwise it restarts at the first for-loop but incremented by one. First go it should search the letter "p", second run it should search the letter "A" and so on.
Result: p5A5SSW55o55rod
Expected Result: p5A5SSW5o5R5o5d
In the end I will also want to rotate all the characters, but thats for another task, I think I can just use either if-statement or a switch to do that. If it ends up on a 5, do nothing, otherwise rotate.
I hope I made myself clear and provided you with all the relevant information, otherwise just holler in the comments.
Thanks in advance.
#include <string>
#include <sstream>
const string vowel = "AOao";
string encrypt, decrypt;
encrypt = "pASSWoRod";
decrypt = encrypt;
for (int i=0; i<encrypt.length(); i++){
for (int j=0; j<vowel.length(); j++){
if (encrypt[i] == vowel[j]){
decrypt.insert(decrypt.find(vowel[j]), 1, '5');
decrypt.insert(decrypt.find(vowel[j]) + 1, +1, '5');
}
}
}
return decrypt;
}
find, when not proved a starting point, always finds the first instance.
Searching and keeping track of the string length and where you've already inserted characters is much harder than it seems at first glance (as you've noticed).
Build the result from scratch instead of inserting characters into an initial string.
Also, implement this function (actual implementation left as an exercise):
bool is_vowel(char c);
and then
std::string encrypt = "pASSWoRod";
std::string decrypt;
for (auto c: encrypt)
{
if (is_vowel(c))
{
decrypt += '5';
decrypt += c;
decrypt += '5';
}
else
{
decrypt += c;
}
}
find with one argument starts from the beginning. You would need the other find.
However maintaing an index in decrypt, one no longer would need a find.
int encryptI = 0;
for (int i = 0; i < decrypt.length(); i++, encyptI++){
for (int j=0; j<vowel.length(); j++){
if (decrypt[i] == vowel[j]){
//encryptI = encrypt.find(vowel[j], encryptI);
encrypt.insert(encryptI, 1, '5');
encrypt.insert(encryptI + 1, +1, '5');
encryptI += 2;
break;
}
}
}
The code could be nicer.
string encrypt;
for (int i = 0; i < decrypt.length(); i++, decyptI++){
char ch = decrypt[i];
if (vowel.find(ch) != string::npos) {
encrypt.append('5');
encrypt.append(ch);
encrypt.append('5');
} else {
encrypt.append(ch);
}
}
Your find is stopping at the first instance of the vowel, 'o'. You should include a starting position for your find so you ignore the part of the decrypt string you've already analyzed.

Continually erasing a string leads to an infinite loop

What I am trying to do is take a path in and then continually erase the path directory by directory, checking to see if it is a symbolic link at any point. Here is what I have:
static bool isLinkDirInSymLink (std::string linkPath)
{
DIR *baseDir;
struct dirent *currentDir;
do
{
baseDir = opendir(linkPath.c_str());
if (baseDir)
{
currentDir = readdir(baseDir);
if (currentDir->d_type == DT_LNK)
return true;
}
linkPath.erase (linkPath.find_last_of("/") + 1, linkPath.find_first_of("\0"));
} while (strcmp(linkPath.c_str(), "") != 0);
return false;
}
This gets stuck in an infinite loop. When I run the program in gdb what happens is I send in a linkPath of /home/user/test/linktest/out/mDirs/testDir1/test, when this successfully erases and I am left with is /home/user/test/linktest/out/mDirs/testDir1, however this is where the infinite loop begins. Even though this is in the same format as the first path when it goes into erase, nothing happens. I have tried many different variations of erase from here but none seem to work. I have also tried linkPath.append('\0') because I thought maybe it was an issue with the null character at the end.
Thanks everyone, this is what I ended up with:
char realPath[MAX_FILELENGTH];
do
{
if (realpath (linkPath.c_str(), realPath) != NULL)
if (strcmp(linkPath.c_str(), realPath) != 0)
return true;
size_t eraseFrom = linkPath.rfind('/');
if (std::string::npos != eraseFrom)
linkPath.erase(eraseFrom);
} while ( !linkPath.empty() );
return false;
Because of the + 1 in you erase call, you are erasing characters from one past the / to just before the end of the string, erasing the following characters:
/home/user/test/linktest/out/mDirs/testDir1/test\0
^^^^
The first iteration of the loop will remove test, leaving you with /home/user/test/linktest/out/mDirs/testDir1/. All subsequent calls to erase will do nothing, because there are zero characters between / and \0.
You should remove the + 1 from linkPath.find_last_of("/") + 1 in your erase call, so that the trailing slash is removed as well.
Moreover, the erase(size_t, size_t) overload actually takes the length of the part to erase as the second argument - find_first_of returns the index of the found character, not an iterator to it. Your code works only by accident. Use std::string::npos, which will erase everything until the end, instead of the position of the \0 character (which may not be present in the string if you haven't called c_str() yet).
linkPath.find_last_of("/") + 1
Should just be;
linkPath.find_last_of("/")
The first erase leaves a trailing / in place, so the next erase attempts to erase from the end of the string to the end, hence the loop. The erase should include the directory separator /.
The linkPath.find_first_of("\0") is not needed, you can just use npos to remove to the end of the string. The use of the find_first_of gives a size type result, so the following form of erase is used basic_string& erase( size_type index = 0, size_type count = npos );.
I guess that you missed the trailing slash when looking in the debugger. This could be better:
linkPath.erase (linkPath.begin()+linkPath.find_last_of("/"), linkPath.end());
There was another problem that wrong overload of std::string::erase was called: #1 in this list ("pos+len"), while you likely intended #3 ("range"). That happens because std::string::find_last_of returns size_t, not an iterator. Alternatively, to save typing you can use this:
linkPath.resize(linkPath.find_last_of("/"));
I think you mean something as the following
#include <iostream>
#include <string>
int main()
{
std::string linkPath( "http://stackoverflow.com/questions/31590945/"
"continually-erasing-string-leads-to-infinite-loop" );
do
{
std::cout << linkPath << std::endl;
auto n = linkPath.rfind( '/' );
n = n == std::string::npos ? 0 : n;
linkPath.erase( n );
} while ( !linkPath.empty() );
}
The program output is
http://stackoverflow.com/questions/31590945/continually-erasing-string-leads-to-infinite-loop
http://stackoverflow.com/questions/31590945
http://stackoverflow.com/questions
http://stackoverflow.com
http:/
http:
Of course you can modify the code as you like. It demonstrates an approach to the task.
As for your code then this call
linkPath.find_first_of("\0")
will always return std::string::npos. So it does not male sense.
And using this expression
linkPath.find_last_of("/") + 1
will always keep the first found character '/' in the string.
A more correct and simplified implementation
static bool isLinkDirInSymLink(std::string linkPath) {
DIR * baseDir;
struct dirent * currentDir;
do {
baseDir = opendir(linkPath.c_str());
if (baseDir) {
currentDir = readdir(baseDir);
if (currentDir->d_type == DT_LNK) return true;
}
std::string::size_type it = linkPath.rfind('/');
if (it != std::string::npos) linkPath.erase(it);
} while (!linkPath.empty());
return false;
}

Finding all occurrences using rfind, flow challenges?

Following a c++ tutorial and teaching about find() the following code was implemented to search for all the "cat" occurrences in a string:
std::string input;
std::size_t i = 0, x_appearances = 0;
std::getline(std::cin,input);
for(i = input.find("cat",0); i != std::string::npos; i=input.find("cat", i))
{
++x_appearances;
++i; //Move past the last discovered instance to avoid finding the same string
}
Then the tutorial challenges the apprentice to change find() for rfind(), and that's where the problems came in, first I tried what seemed to be the obvious approach:
for(i = input.rfind("cat",input.length()); i != std::string::npos; i=input.rfind("cat", i))
{
++x_appearances;
--i; //Move past the last discovered instance to avoid finding the same string
}
but with this solution I fell into an infinite loop. Then I discovered that it was happening because the increment is performed before the condition check, and that the increment rfind() was always finding a match even with i==std::string::npos (if the match is on the beginning of the string, for example "cats"). My final solution came to be:
int n=input.length();
for(i = input.rfind("cat",input.length()); n>0 && i!=std::string::npos; i=input.rfind("cat", i))
{
++x_appearances;
n=i;
--i; //Move past the last discovered instance to avoid finding the same string
}
With n I can keep the track of the position in the string, and with it exit the for loop when the entire string had been searched.
So my question is: Is my approach correct? Did I need an extra variable or is there any other simpler way of doing this?
for(i = input.rfind("cat",input.length()); i != std::string::npos; i=input.rfind("cat", i))
{
++x_appearances;
--i; //Move past the last discovered instance to avoid finding the same string
}
The problem with the above is the --i inside the loop. Suppose the input string starts with "cat". Your algorithm will eventually find that "cat" with i being 0. Since you've declared i as a std::size_t, subtracting 1 from 0 results in the largest possible std::size_t. There's no warning, no overflow, no undefined behavior. This is exactly how unsigned integers must work, per the standard.
Somehow you need to handle this special case. You could use an auxiliary variable and a more convoluted test in your loop. An alternative is to keep your code simple and at the same time make it blatantly obvious you are explicitly handling this special case:
for (i = input.rfind("cat"); i != std::string::npos; i=input.rfind("cat", i-1))
{
++x_appearances;
// Finding "cat" at the start means we're done.
if (i == 0) {
break;
}
}
Note also that I've changed the loop statement a bit. The default value for pos is std::string::npos, which means search from the end of the string. There's no need for that second argument with the initializer. I also moved the --i into the update part of the for loop, changing input.rfind("cat",i) to input.rfind("cat",i-1). Since i is always positive at this point, there's no danger in subtracting one.

Vector out of range- C++

I'm working on a project that requires me to make a game of Hangman in c++. I have most of it working, but I'm stuck at printing out the part of the word guessed correctly each time after the user enters a guess. I've created a class to represent a game of hangman, and in this class are the methods that determine what to do for a guess. If a guess is found in any location in the randomly chosen word from a dictionary, I save that char to the same location in a vector called currentWord. currentWord is initialized in the constructor to be contain "_" for the length of the randomly chosen word(that way it is the same size as the word and I can just update it as the user types a guess). For example, if the word is "semicolonialism", and the user's first guess is 'i', I want to replace the '_' in the currentWord vector with the letter 'i'.
string tempWord = word;
for (int i = 0; i < tempWord.size(); i++) {
u_long location = tempWord.find(guess);
currentWord->at(location) = tempWord[location];
tempWord[location] = '_';
}
What I've tried to do is store the member variable "word" in a temporary variable called tempWord. Then I iterate from 0 to the length of tempword. I use tempWord.find(guess) to find the location in tempWord that is a match for the guess, store that into a variable called location, and then update the currentWord vector at that location to equal the tempWord at that location. Since this would only work for the first time the matching char is found, I then change tempWord[location] to '_', that way the next time through, location will be different. But by doing this I some times get the out of range error. If I comment out
tempWord[location] = '_';
then I don't see this error, but only the first occurrence is replaced. Even though I get this out of bounds error, I can see in the debugger that each occurrence is properly replaced in the currentWord vector. This leaves me very confused, so any help would be greatly appreciated! Thanks
EDIT
Thanks to rapptz suggestion to check if location is equal to std::string::npos, I finally have it working. Here's the updated code segment with that check in place:
string tempWord = word;
for (int i = 0; i < tempWord.size(); i++) {
u_long location = tempWord.find(guess);
if (location != std::string::npos) {
currentWord->at(location) = tempWord[location];
tempWord[location] = '_';
}
}
I really liked Tristan's suggestion too, and will do that tomorrow most likely. Once I do, I'll post the updated code as well in case someone else might find it useful. Thanks again!
Was going to post this as a comment but it's easier in a bigger text box! You can avoid both the tempWord copy and the for loop like this:
std::string::size_type location = 0, start_pos = 0; // int would be fine, tbh
while ( (location = word.find(guess, start_pos)) != std::string::npos) {
currentWord.at(location) = word[location];
start_pos = location;
}
My guess is that tempword.find(guess) starts from 1 to the lenght of word, not 0. Please share that function too.

C++ std::string::find always returns npos?

I'm trying to get this function to cut up a string, and then return it without whitespace and all lowercase. And to do this I'm trying to find a " " to see if a string, "The Time Traveller (for so it will be convenient to speak of him)", contains a space.
The code is as follows, passing in the string above to this function. It always returns string::npos. Any idea about the problem?
string chopstring(string tocut){
string totoken = "";
int start = 0;
while(tocut[0] == ' ' || tocut[0] == 10 || tocut[0 == 13]){
tocut.erase(0);
}
int finish = 0;
finish = tocut.find(" ", start);
if (finish == string::npos){
cout << "NPOS!" << endl;
}
for (int i = start; i < finish; i++){
totoken += tocut[i];
}
tocut.erase(start, finish);
return tokenize(totoken);
}
tocut.erase(0) is erasing all of tocut. The argument is the first character to erase, and the default length is "everything".
tocut[0 == 13] should probably be tocut[0] == 13. Those are very different statements. Also, please compare with character values ('\t') instead of integers. Incidentally, this in conjunction with the previous is your actual problem: tocut[0 == 13] becomes tocut[false], which is tocut[0], which is true. So the loop runs until tocut is empty, which is immediately (since you erase it all overzealously in the first go).
The net effect of the above two bugs is that when you reach the find statement, tocut is the empty string, which does not contain a space character. Moving on...
You can use the substr function instead of your loop to migrate from tocut to totoken.
Your last tocut.erase(start, finish) line isn't doing anything useful, since tocut was pass-by-value and you immediately return after that.
Actually, the majority of the code could be written much simpler (assuming my understanding that you want to remove all spaces is correct):
string chopstring(string tocut) {
std::string::size_type first(tocut.find_first_of(" \n\r"));
if (first != tocut.npos) {
tocut.substr(first);
}
tocut.erase(std::remove(tocut.begin(), tocut.end(), ' '), tocut.end());
return tokenize(tocut);
}
If you actually want to remove all whitespace, you probably want to use std::remove_if() with a suitable predicate.