How can I reach the second word in a string? - c++

I'm new here and this is my first question, so don't be too harsh :]
I'm trying to reverse a sentence, i.e. every word separately.
The problem is that I just can't reach the second word, or even reach the ending of a 1-word sentence. What is wrong?
char* reverse(char* string)
{
int i = 0;
char str[80];
while (*string)
str[i++] = *string++;
str[i] = '\0'; //null char in the end
char temp;
int wStart = 0, wEnd = 0, ending = 0; //wordStart, wordEnd, sentence ending
while (str[ending]) /*####This part just won't stop####*/
{
//skip spaces
while (str[wStart] == ' ')
wStart++; //wStart - word start
//for each word
wEnd = wStart;
while (str[wEnd] != ' ' && str[wEnd])
wEnd++; //wEnd - word ending
ending = wEnd; //for sentence ending
for (int k = 0; k < (wStart + wEnd) / 2; k++) //reverse
{
temp = str[wStart];
str[wStart++] = str[wEnd];
str[wEnd--] = temp;
}
}
return str;
}

Your code is somewhat unidiomatic for C++ in that it doesn't actually make use of a lot of common and convenient C++ facilities. In your case, you could benefit from
std::string which takes care of maintaining a buffer big enough to accomodate your string data.
std::istringstream which can easily split a string into spaces for you.
std::reverse which can reverse a sequence of items.
Here's an alternative version which uses these facilities:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <vector>
std::string reverse( const std::string &s )
{
// Split the string on spaces by iterating over the stream
// elements and inserting them into the 'words' vector'.
std::vector<std::string> words;
std::istringstream stream( s );
std::copy(
std::istream_iterator<std::string>( stream ),
std::istream_iterator<std::string>(),
std::back_inserter( words )
);
// Reverse the words in the vector.
std::reverse( words.begin(), words.end() );
// Join the words again (inserting one space between two words)
std::ostringstream result;
std::copy(
words.begin(),
words.end(),
std::ostream_iterator<std::string>( result, " " )
);
return result.str();
}

At the end of the first word, after it's traversed, str[wEnd] is a space and
you remember this index when you assign ending = wEnd.
Immediately, you reverse the characters in the word. At that point,
str[ending] is not a space because you included that space in the
letter-reversal of the word.
Depending on whether there are extra
spaces between words in the rest of the input, execution varies from this point, but it does eventually end with
you reversing a word that ended at the null terminator on the string
because you end the loop that increments wEnd on that null terminator and
include it in the final word reversal.
The very next iteration walks off of
the initialized part of the input string and the execution is undetermined from there because, heck, who knows what's in that array (str is stack-allocated, so it's whatever's sitting around in the memory occupied by the stack at that point).
On top of all of that, you don't update wStart except in the reversal loop,
and it never moves to wEnd all the way (see the loop exit condition), so come to think of it, you're never getting past that first word. Assuming that was fixed, you'd still have the problem I outlined at first.
All this assumes that you didn't just send this function something longer than 80 characters and break it that way.
Oh, and as mentioned in one of the comments on the question, you're returning stack-allocated local storage, which isn't going to go anywhere good either.

Hoo, boy. Where to start?
In C++, use std::string instead of char* if you can.
char[80] is an overflow risk if string is input by a user; it should be dynamically allocated. Preferably by using std::string; otherwise use new / new[]. If you meant to use C, then malloc.
cmbasnett also pointed out that you can't actually return str (and get the expected results) if you declare / allocate it the way you did. Traditionally, you'd pass in a char* destination and not allocate anything in the function at all.
Set ending to wEnd + 1; wEnd points to the last non-null character of the string in question (eventually, if it works right), so in order for str[ending] to break out of the loop, you have to increment once to get to the null char. Disregard that, I misread the while loop.
It looks like you need to use ((wEnd - wStart) + 1), not (wStart + wEnd). Although you should really use something like while(wEnd > wStart) instead of a for loop in this context.
You also should be setting wStart = ending; or something before you leave the loop, because otherwise it's going to get stuck on the first word.

Related

Erase words from a string (in C++)

I want to delete some words from a string but my code doesn't work . I don't have any errors or warnings , but I'm thinking that my string becomes empty. Could someone help me with this? I tried to convert my initial strings into 2 vectors, so that I can navigate more easily then
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
int main()
{
string s("Somewhere down the road");
string t("down");
istringstream iss(s);
vector <string> plm;
vector <string> plm2;
do
{
string sub;
iss >> sub;
plm.push_back(sub);
} while (iss);
for(unsigned int i=0 ; i<plm.size();i++){
cout<<plm[i];}
istringstream ist(t);
do
{
string subb;
ist >> subb;
plm2.push_back(subb);
} while (ist);
for(int i=0;i<plm.size();i++){
for(int j=0;j<plm2.size();i++){
{if (plm[i]==plm2[j])
plm.erase(plm.begin()+j);}}}
for(int i=0 ; i<plm.size();i++)
cout<<plm[i];
}
Warning: this is really just a comment that's too long to fit in a comment field. Oh, and a bit of a rant at that.
I'm sure glad we have these modern languages to make life so much easier than it was decades ago. Consider, for example, what this job looked like an the long-since moribund SNOBOL 4 programming language:
s = 'somewhere down the road'
del s 'down' = :s(del)
OUTPUT = s
God, it's nice that we've since made so much progress that we don't have to deal with 3 whole lines of code, and we can now do the job with only 52 lines instead (oh, except that the 52 lines don't actually work, but let's ignore that for the moment).
I guess, in fairness, we can do the job a little more compactly in C++ though. One obvious way would be with std::remove_copy, some stream iterators, and a stringstream or two:
std::istringstream input("somewhere down the road");
std::string del_str("down");
std::istream_iterator<std::string> in(input), end;
std::ostringstream result;
std::remove_copy(in, end, std::ostream_iterator<std::string>(result, " "), del_str);
std::cout << result.str();
There is no benefit in converting to vector - string itself already provides all that is necessary for what you want to do. Anyway, do it this way:
vector<char> v;
v.assign(s.c_str(), s.c_str() + s.length()); // without...
v.assign(s.c_str(), s.c_str() + s.length() + ); // including...
// ... terminating null character
Now it gets easy:
size_t pos = s.find(t);
if(pos != string::npos)
{
s.erase(pos, t.length());
}
This does not care, however, about leaving multiple whitespace or if t is not an entire word within s (e. g. t = "down"; s = "I'm going to downtown."; would result in s == "I'm going to town."), but you did not do so either...
First problem is, if std::string::erase is called only with the beginning position, it erases everything until the end of string.
Second problem is, that the code will just erase all letters which are in the second string, one by one. I.e. not the entire word - for that, you would need to check if the entire word matches, and only then erase (the entire length of the word). Ask yourself - what will happen in the code, if e.g. the first two letters will match, but not the rest of the word?
In your second for loop you never incremented j and inside the if (plm[i]==plm2[j]) block you used j instead of i as your offset in erase().
for(int i=0;i<plm.size();i++)
{
for(int j=0;j<plm2.size();j++)//here you need to increment j
{
if (plm[i]==plm2[j])
plm.erase(plm.begin()+i);//here the offset should be i
}
}
Another thing don't use a do...while loop to read from the stringstream and push back on the vector. If the reading fails you will be pushing invalid data to the vector, instead try something like:
string sub;
while(iss >> sub;)
plm.push_back(sub);//only if reading is successful
...//do the same for the other istringstream too
You do not increment j this is the first thing I saw on your code. Write it correctly then if it still doesnt work, then ask!

Cannot get second while to loop properly

I'm making a function that removes elements from a string. However, I cant seem to get both of my loops to work together. The first while loop works flawlessly. I looked into it and I believe it might be because when "find_last_of" isn't found, it still returns a value (which is throwing off my loop). I haven't been able to figure out how I can fix it. Thank you.
#include <iostream>
#include <string>
using namespace std;
string foo(string word) {
string compare = "!##$";
string alphabet = "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
while(word.find_first_of(compare) < word.find_first_of(alphabet)) {
int position = word.find_first_of(compare);
word = word.substr(++position);
}
while(word.find_last_of(compare) > word.find_last_of(alphabet)){
int size = word.length();
word = word.substr(0, --size);
}
return word;
}
int main() {
cout << foo("!!hi!!");
return 0;
}
I wrote it like this so compound words would not be affected. Desired result: "hi"
It's not entirely clear what you're trying to do, but how about replacing the second loop with this:
string::size_type p = word.find_last_not_of(compare);
if(p != string::npos)
word = word.substr(0, ++p);
It's not clear if you just want to trim certain characters from the front and back of word or if you want to remove every one of a certain set of characters from word no matter where they are. Based on the first sentence of your question, I'll assume you want to do the latter: remove all characters in compare from word.
A better strategy would be to more directly examine each character to see if it needs to be removed, and if so, do so, all in one pass through word. Since compare is quite short, something like this is probably good enough:
// Rewrite word by removing all characters in compare (and then erasing the
// leftover space, if any, at the end). See std::remove_if() docs.
word.erase(std::remove_if(word.begin(),
word.end(),
// Returns true if a character is to be removed.
[&](const char ch) {
return compare.find(ch) != compare.npos;
}),
word.end());
BTW, I'm not sure why there is both a compare and alphabet string in your example. It seems you would only need to define one or the other, and not both. A character is either one to keep or one to remove.

How to count whitespace occurences in a string in c++

I have a project for my advanced c++ class that's supposed to do a number of things, but I'm trying to focus on this function first, because after it works I can tweak it to fulfill the other needs. This function searches through a file and performs a word count by counting the number of times ' ' appears in the document. Maybe not accurate, but it'll be a good starting place. Here's the code I have right now:
void WordCount()
{
int count_W = 0; //Varaible to store word count, will be written to label
int i, c = 0; //i for iterator
ifstream fPath("F:\Project_1_Text.txt");
FileStream input( "F:\Project_1_Text.txt", FileMode::Open, FileAccess::Read );
StreamReader fileReader( %input );
String ^ line;
//char ws = ' ';
array<Char>^ temp;
input.Seek( 0, SeekOrigin::Begin );
while ( ( line = fileReader.ReadLine() ) != nullptr )
{
Console::WriteLine( line );
c = line->Length;
//temp = line->ToCharArray();
for ( i = 0; i <= c; i++)
{
if ( line[i] == ' ' )
count_W++;
}
//line->ToString();
}
//Code to write to label
lblWordCount->Text = count_W.ToString();
}
All of this works except for one problem. When I try to run the program, and open the file, I get an error that tells me the Index is out of bounds. Now, I know what that means, but I don't get how the problem is occurring. And, if I don't know what's causing the problem, I can't fix it. I've read that it is possible to search through a string with a for loop, and of course that also holds true for a char array, and there is code in there to perform that conversion, but in both cases I get the same error. I know it is reading through the file correctly, because the final program also has to perform a character count (which is working), and it read back the size of each line in the target document perfectly from start to finish. Anyway, I'm out of ideas, so I thought I'd consult a higher power. Any ideas?
Counting whitespace is simple:
int spaces = std::count_if(s.begin(), s.end(),
[](unsigned char c){ return std::isspace(c); });
Two notes, though:
std::isspace() cannot be used immediately with char because char may be signed and std::isspace() takes an int which is required to be positive.
This counts the number of spaces, not the number of words (or words - 1): words may be separated by sequences of spaces consisting of more than one consecutive space.
It could be your loop. You're going from i=0 to i=c, but i=c is too far. You should go to i=c-1:
for ( i=0; i<c; i++)

Why does this string search and replace segfault?

colours is a std::map<string, string> where the first element of each pair is a 2 letter std::string colour code, and the second element is the 7 character std::string shell escape code for that colour.
size_t i;
for(map<string, string>::iterator iter = colours.begin(); iter != colours.end(); iter++) {
while((i = text.find(iter->first)) != string::npos) {
text.replace(i, i + sizeof(iter->first), iter->second);
}
}
When this code is run, the program segfaults. My best guess is that it is something to do with the length of the replacement string being longer than the length of the string it's replacing, but to the best of my knowledge that can only cause segfaults with char *, not std::string.
string::replace takes a start position and a length, not a start position and an end position. Also sizeof does not return the length of a string. So the third line should probably read:
text.replace(i, iter->first.length(), iter->second);
For what it's worth, sizeof returns the amount of stack space consumed by the object, which is known at compile time. So it cannot vary from call to call, which the string's length does.
#luqui already answered why it segfaults, but I wanted to add something else.
Be careful not to end up in an infinite loop in the case where you your replacement string contains the search string. You can avoid this by passing string::find() a position to start searching. This will also improve efficiency as your current solution could degenerate to O(N2).

How to get only first words from several C++ strings?

I have several C++ strings with some words. I need to get the first word from every string. Then I have to put all of them into a char array. How can I do it?
Here is one way of doing it...
// SO2913562.cpp
//
#include <iostream>
#include <sstream>
using namespace std;
void getHeadWords(const char *input[]
, unsigned numStrings
, char *outBuf
, unsigned outBufSize)
{
string outStr = "";
for(unsigned i = 0; i<numStrings; i++)
{
stringstream ss(stringstream::in|stringstream::out);
ss<<input[i];
string word;
ss>>word;
outStr += word;
if(i < numStrings-1)
outStr += " ";
}
if(outBufSize < outStr.size() + 1)//Accomodate the null terminator.
//strncpy omits the null terminator if outStr is of the exact same
//length as outBufSize
throw out_of_range("Output buffer too small");
strncpy(outBuf, outStr.c_str(), outBufSize);
}
int main ()
{
const char *lines[] = {
"first sentence"
, "second sentence"
, "third sentence"
};
char outBuf[1024];
getHeadWords(lines, _countof(lines), outBuf, sizeof(outBuf));
cout<<outBuf<<endl;
return 0;
}
But note the above code has marginal error checking and may have security flaws. And needless to say my C++ is a bit rusty. Cheers.
I'll assume it's homework, so here is a general description:
First, you need to allocate enough space in your char array. In homework, you are usually told the maximum size. That maximum has to be enough for all the first words.
Now, you need to have an index for the insertion point in that array. Start it at zero.
Now go over your strings in order. In each, move an index forward from 0 until you see a \0 or a space (or other delimiter. Insert the character at the insertion point in the result array and increase that index by 1.
If you have encountered a space or a \0, you've found your first word. If you were on the last string, insert a \0 at the insertion point and you're done. If not, insert a space and move to the next string.
what compiler are you using?
converting to a chararray is the first thing to look for.
after done that, you can easily step through your array (and look for spaces)
something like this:
while (oldarray[i++] != ' ')
yournewarray[j++];
i think you gotta figure out the rest yourself, since this looks like some homework for school :)
Assuming this is homework, and that when you say "strings" you mean simple null-delimited arrays of char (and not std::string):
define your strings
define your resulting char array
for each string
find the offset of the first char that is not in the first word
append that many bytes of the string to the result array
If this is not homework, give us a little code to start with and we'll fill in the blanks.