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.
Related
I am currently making a text game in c++. I am using a function that prints text one character at the time (to give a "narration" effect), which also goes to a new line to some condition defined by the function.
Here is the function:
void smart_print(const std::string& str, int spacer)//str is the printed message. spacer is the amount of space you want at the beginning and at the end of the cmd window
{
int max = Console::BufferWidth - (spacer * 2);
int limit = max;
ut.spacer(5);//this prints 5 spaces
for (int i = 0; i != str.size(); ++i)//this loop prints one character of the string every 50 milliseconds. It also checks if the limit is exceeded. If so, print new line
{
if (limit < 0)
{
cout << endl;
ut.spacer(5);
limit = max;
}
limit--;
std::cout << str[i];
Sleep(50);
}
}
The problem with this function, is that it chops the words, because it does a new line everytime the "limit" variable is less than 0, regardless if there is an incomplete word or not.
I made a sort of scheme to try to figure out how it should work correctly, but i can't manage to "translate" it into code.
1) Analyze the string, and check how long is the first word
2) Count the characters and stop counting when there is a space
3) Calculate if it can print the word (by subtracting the number of letters to max)
4) If the limit is exceeded, go to new line. Otherwise proceed to print the word one letter at the time
I really can't manage to make such function. I hope someone can help me out :P
Thanks in advance.
I think you should do something like checking if the current character is blank, using the std::isspace method like this:
// inside your for
if (limit < 0 && isspace(str[i]))
{
cout << endl;
ut.spacer(5);
limit = max;
}
limit--;
if(!isspace(str[i])) std::cout << str[i];
Sleep(50);
Note: I haven't tested the code so I am not 100% sure if it works correctly.
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).
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;
}
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.
I'm iterating through an array of chars to do some manipulation. I want to "skip" an iteration if there are two adjacent characters that are the same.
e.g. x112abbca
skip----------^
I have some code but it's not elegant and was wondering if anyone can think of a better way? I have a few case's in the switch statement and would be happy if I didn't have to use an if statement inside the switch.
switch(ent->d_name[i])
{
if(i > 0 && ent->d_name[i] == ent->d_name[i-1])
continue;
case ' ' :
...//code omited
case '-' :
...
}
By the way, an instructor once told me "avoid continues unless much code is required to replace them". Does anyone second that? (Actually he said the same about breaks)
Put the if outside the switch.
While I don't have anything against using continue and break, you can certainly bypass them this time without much code at all: simply revert the condition and put the whole switch statement within the if-block.
Answering the rectified question: what's clean depends on many factors. How long is this list of characters to consider: should you iterate over them yourself, or perhaps use a utility function from <algorithm>? In any case, if you are referring to the same character multiple times, perhaps you ought to give it an alias:
std::string interesting_chars("-_;,.abc");
// ...
for (i...) {
char cur = abc->def[i];
if (cur != prev || interesting_chars.find(cur) == std::string::npos)
switch (current) // ...
char chr = '\0';
char *cur = &ent->d_name[0];
while (*cur != '\0') {
if (chr != *cur) {
switch(...) {
}
}
chr = *cur++;
}
If you can clobber the content of the array you are analyzing, you can preprocess it with std::unique():
ent->erase(std::unique(ent->d_name.begin(), ent->d_name.end()), ent.end());
This should replace all sequences of identical characters by a single copy and shorten the string appropriately. If you can't clobber the string itself, you can create a copy with character sequences of just one string:
std::string tmp;
std::unique_copy(ent->d_name.begin(), ent->d_name.end(), std::back_inserter(tmp));
In case you are using C-strings: use std::string instead. If you insist in using C-strings and don't want to play with std::unique() a nicer approach than yours is to use a previous character, initialized to 0 (this can't be part of a C-string, after all):
char previous(0);
for (size_t i(0); ent->d_name[i]; ++i) {
if (ent->d_name[i] != previous) {
switch (previous = ent->d_name[i]) {
...
}
}
}
I hope I understand what you are trying to do, anyway this will find matching pairs and skip over a match.
char c_anotherValue[] = "Hello World!";
int i_len = strlen(c_anotherValue);
for(int i = 0; i < i_len-1;i++)
{
if(c_anotherValue[i] == c_anotherValue[i+1])
{
printf("%c%c",c_anotherValue[i],c_anotherValue[i+1]);
i++;//this will force the loop to skip
}
}