Continually erasing a string leads to an infinite loop - c++

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;
}

Related

Splitting up a string from end to start into groups of two in C++

I was curious about the way I could make a program, that takes a string, then detects the end of it, and then starts splitting it up "from end toward the start", into the groups of two?
For instance, the user enters mskkllkkk and the output has to be m sk kl lk kk.
I tried to search the net for the tools I needed, and got familiar with iterators, and tried to use them for this purpose. I did something like this:
#include "iostream"
#include "string"
#include "conio.h"
int main() {
int k=0,i=-1;
std::string str1;
std::string::iterator PlaceCounter;
std::cin >> str1;
PlaceCounter = str1.end();
for (PlaceCounter; PlaceCounter != str1.begin(); --PlaceCounter)
{
++k;
if (k % 2 == 0 && k-1 != 0) {
++i;
str1.insert(str1.end()-k-i,' ');
}
}
std::cout << str1;
_getch();
return 0;
}
At first, it seemed to be working just fine when I entered a couple of arbitrary cases(Such thing can exactly be used in calculators to make the numbers more readable by putting each three digits in one group, from the end toward the start), But suddenly when I entered this: jsfksdjfksdjfkdsjfskjdfkjsfn , I got the error message:"String iterator not decrementable".
Presumably I need to study much more pages of my book for C++ to be able to solve this myself, but for now I'm just being super-curious as a beginner. Why is that error message? Thanks in advance.
When you insert() into your string the iterators to it may get invalidated. In particular all iterators past the insertion point should be considered invalidated in all cases but also all iterators get invalidated if the std::string needs to get more memory: the internal buffer will be replaced by a bigger one, causing all existing iterator (and references and pointers) to string elements to be invalidated.
The easiest fix to the problem is to make sure that the string doesn't need to allocate more memory by reserve()ing enough space ahead of time. Since you add one space for every two characters, making sure that there is space for str1.size() + str1.size() / 2u characters should be sufficient:
str1.reserve(str1.size() + str1.size() / 2u);
for (auto PlaceCounter = str1.end(); PlaceCounter != str1.begin(); --PlaceCounter) {
// ...
}
Note that your algorithm is rather inefficient: it is an O(n2). The operation can be done with O(n) complexity instead. You'd resize the string to the appropriate size right from the start, filling the tail with some default characters and then copy the content moving from the end directly to the appropriate location.
str1.insert(str1.end()-k-i,' ');
This modifies the string the loop is iterating over. Specifically, this inserts something into the string.
With a std::string, much like a std::vector, insertion into a string will (may) invalidate all existing iterators pointing to the string. The first insertion performed by the shown code results in undefined behavior, as soon as the existing, now invalidated, iterators are referenced afterwards.
You will need to either replace your iterators with indexes into the string, or instead of modifying the existing string construct a new string, leaving the original string untouched.
Here is a possible C++ approach to try. From my tool bag, here is how I insert commas into a decimal string (i.e. s is expected to contain digits):
Input: "123456789"
// insert comma's from right (at implied decimal point) to left
std::string digiCommaL(std::string s)
{
// Note: decrementing a uint (such as size_t) will loop-around,
// and not underflow. Be sure to use int ...
int32_t sSize = static_cast<int32_t>(s.size()); // change to int
// ^^^^^-----------_____
if (sSize > 3) vvvvv
for (int32_t indx = (sSize - 3); indx > 0; indx -= 3)
s.insert(static_cast<size_t>(indx), 1, ',');
return(s);
}
Returns: "123,456,789"

member function erase() not working in a loop

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).

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.

Passing an array to a function, but if it doesn't return true I want to pass an extra digit?

So for my code I want to pass an argument to a function and if it doesn't return true, I want it to pass the next index digit in the int array.
So if areaIntA[0] = 0; doesn't return true, I want it to pass areaIntA[0][1] = 01; and if that doesn't return true, areaIntA[0][1][2] = 012; etc...
My updated code:
areaInt = areaIntA[0];
do {
areaCheck = isRegistered(file, areaInt);
if (areaCheck != 1)
{
areaInt = areaIntA[i] * 10 + areaIntA[i+1];
i++;
}
} while (areaCheck != 1);
cout << areaCheck << endl;
This compiles but it shows a segmentation error, anyone know why?
Have the function take in a list (vector). for every call add a new element to the end.
eg.
List< int?> areaIntList;
do {
areaIntList.Add(areaInt[i]);
areaCheck = isRegistered(file, areaInt[i]);
if (areaCheck != 1)
{
i++;
}
} while (areaCheck != 1);
isRegistered takes in a List of your used type.
You're weirdly equating array integer values in a sort of string context. That usually means base-10 math.
Try:
int val = 0;
do {
val = 10 * val + areaInt[i];
areaCheck = isRegistered(file, val);
if (areaCheck != 1)
i++;
} while (areaCheck != 1);
On the first pass, val will be the value of areaInt[0]; On the second pass, it will be areaInt[0] followed by areaInt[1] (e.g. 0 and 1 become 1, 1 and 2 become 12). And so on.
My understanding of your question is this:
isRegistered is a function that takes a file and a variable-length string of digits, returning 1 if the string is registered and 0 otherwise.
areaInt is a C-style string like "31526".
What you want is to see if "3" is registered; if not, see if "31" is registered; if not, try "315", etc until all the digits of areaInt are exhausted.
The end result should be a substring of areaInt which is the shortest registered string, or an error if no registered string was found.
This is how I'd do it.
If the input string areaInt is empty, return an error.
Loop a counter i from 1 to the length of the string:
Construct a test string s from the first i characters in areaInt.
Test if s is registered. If it is, break out of the loop and return s. If not, continue the loop.
If the loop completes at the end of areaInt and no registered string has been found, return an error.
Note. Make sure you stop at the end of areaInt. Otherwise if you get to the end of areaInt and still haven't found a string that is registered, then you will overrun the buffer and try to read an element not in the string.

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.