C++ C-string has (apparently) no content to display - c++

First of all, I am nothing but new to both programming and Stack Overflow.
I am self-studying with Schaum's outline for Programming with C++ and I have some issues with problem 8.24 (solutions are given to almost every problem in the book, but I want to know why my code in particular isn't working as expected).
You are supposed to be given a c-string and return the given string, but with all its tokens in reverse order (but keeping the natural order of the token itself).
That is, given "Enter a sentence" it would show on screen "sentence a Enter".
My code is the following:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char line1[100];
cout << "Enter a sentence (enter \".\" to terminate input):\n";
cin.getline(line1,100,'.');
char line2[strlen(line1) + 1]; // we add 1 for the empty char that ends every c string
int char_count = strlen(line1); //strlen() does not include the empty char
char* p = strtok(line1," ");
while (p)
{
char_count -= strlen(p); // we substract p's len to start adding its chars
for (int i = 0; i <= strlen(p); i++)
line2[char_count + i] = p[i]; // we then add the chars themselves
if ((char_count - 1) > 0)
line2[--char_count] = ' '; // a blanck space is needed between the different tokens
p = strtok(NULL, " ");
}
cout << "\n" << line2 << "\n";
}

Unfortunately, the code is wrong in many ways. The most obvious thing is the obscurity of the word reversal process (and the fact it is mixed with word iteration).
According to the commenters, you are not using C++. In C++ it would be rather straightforward:
#include <algorithm>
#include <iostream>
#include <string>
void reverse_words(std::string& s) {
/* starting position of the word */
size_t last_pos = 0;
do {
/* find the end of current word */
size_t end_pos = std::min( s.find(' ', last_pos + 1), s.size() );
/* reverse one word inplace */
std::reverse(s.begin() + last_pos, s.begin() + end_pos);
/* advance to the begining of the next word */
last_pos = end_pos + 1;
} while (pos != std::string::npos);
std::reverse(s.begin(), s.end());
}
int main()
{
std::string s = "This is a sentence";
reverse_words(s);
std::cout << s << std::endl;
}
Hopefully, you can see the essence of the method: sequentially find start and finish of each word, reverse letter order in this word and then finally reverse the entire string.
Now, getting back to the C-string question. You can replace std::string::find call with strtok and write your version of std::reverse specialized for C strings (the reversal of the entire string or its part is simpler than reversing the word order and this is also the recommended exercise).
Start from a simpler program which prints out pairs of integers (start_pos and end_pos for each word) using strtok. Then write a reverse procedure and test it also. Finally, combine this word iteration with reverse. I personally think this is the only way to be sure your implementation is correct - being sure in each of its parts and being able to test each part individually.

A lot of improvements have been added to C++ since that book was originally written, and we can do it in a lot cleaner and safer way now. We'll break the problem into two parts:
A function to convert a string into a list of tokens
The main function, which reads the string; reverses it; and prints it.
These functions will be tokenize, which returns a vector of string_view, and main. A string_view is just a class that stores a pointer and a size to some other string. It's efficient because it won't make a copy of the string or allocate any memory. In this case, it's the right tool for the job because we're going to be breaking up an existing string.
#include <string_view>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
auto tokenize(std::string_view line) {
std::vector<std::string_view> tokens;
for (size_t token_size = line.find(' ');
token_size != line.npos;
token_size = line.find(' '))
{
tokens.push_back(line.substr(0, token_size));
line.remove_prefix(token_size + 1);
}
tokens.push_back(line);
return tokens;
}
int main() {
std::string line;
std::getline(std::cin, line);
auto tokens = tokenize(line);
std::reverse(tokens.begin(), tokens.end());
for(auto token : tokens) {
std::cout << token << ' ';
}
std::cout << std::endl;
}
Explaining tokenize
Tokenize takes a string_view as input, and returns a list of the tokens. line.find(' ') will look for a space. If it finds one, it'll return the position of the space; otherwise, it'll return line.npos (which is basically the biggest possible size).
For every token we find, we
get the token via view.substr(0, token_size)
Add the token to the vector via tokens.push_back
Then, we update the line by removing the first token and the corresponding space. This is line.remove_prefix(token_size + 1);
Once there are no more spaces, we'll add the remainder of the line to the vector using tokenize.push_back(line);, and then we'll return the vector of tokens.
Explaining main
We can get the line via std::getline(std::cin, line);, which will read a line from cin and put it in the variable we give it (line). After that, we can read all the tokens in the line using the tokenize function we wrote. We'll reverse the vector of tokens via std::reverse, and then we'll print out all the tokens.

Thanks to each of you.
Seeing your answers I have learnt quite a lot about good programming (both regarding syntax and original ways to solve the problem itself, as Viktor's).
I apologise if I have not given the proper feedback, but again I am (still) unfamiliar with Stack's customs and ''policies''.

Related

Sub-strings and delimiters

I'm trying to get a sentence delimited by certain characters (either a space, comma, or a dot) to check if it's a palindrome. If the input is "hello,potato.", I'll check this symmetry on "hello" alone and then potato alone.
The problem is, while I'm doing the first iteration of the loop that searches for the delimiter, the word "hello" is stored in the sub-sentence, but on the second iteration the word that should be stored as "potato" will be "potato.". And I am unable to remove the "." delimiter from the end of the input string.
for(int i=0;i<sentence.length();i++)
{
if(sentence[i]==' '||sentence[i]=='.'||sentence[i]==',')
{ //the couts is just to help me debug/trace
cout<<"i is now : "<<i<<endl;
if(i==delindex && i==sentence.length()-1)
{
subsentence=sentence.substr(temp+1,subsentence.length()-1);
}
else
{
subsentence=sentence.substr(delindex,i);
cout<<subsentence<<endl;
temp=delindex-1;
delindex=i+1;
}
}
}
What would be the best way to go about this?
god bless you man that strtok is what i have been looking for
Actually, you don't need strtok (and should probably avoid it for various safety reasons), as std::string has a wonderful method called find_first_of which acts pretty much like strtok, as in it accepts a bunch of chars and returns index when it stumbles on any of the chars. However to make robust tokenizer a combination of find_first_of and find_first_not_of is more suitable in this case.
Therefore you could simplify your token searching to:
#include <iostream>
#include <string>
int main()
{
std::string sentence = "hello,potato tomato.";
std::string delims = " .,";
size_t beg, pos = 0;
while ((beg = sentence.find_first_not_of(delims, pos)) != std::string::npos)
{
pos = sentence.find_first_of(delims, beg + 1);
std::cout << sentence.substr(beg, pos - beg) << std::endl;
}
}
https://ideone.com/rhMyvG

Splitting sentences and placing in vector

I was given a code from my professor that takes multiple lines of input. I am currently changing the code for our current assignment and I came across an issue. The code is meant to take strings of input and separate them into sentences from periods and put those strings into a vector.
vector<string> words;
string getInput() {
string s = ""; // string to return
bool cont = true; // loop control.. continue is true
while (cont){ // while continue
string l; // string to hold a line
cin >> l; // get line
char lastChar = l.at(l.size()-1);
if(lastChar=='.') {
l = l.substr(0, l.size()-1);
if(l.size()>0){
words.push_back(s);
s = "";
}
}
if (lastChar==';') { // use ';' to stop input
l = l.substr(0, l.size()-1);
if (l.size()>0)
s = s + " " + l;
cont = false; // set loop control to stop
}
else
s = s + " " + l; // add line to string to return
// add a blank space to prevent
// making a new word from last
// word in string and first word
// in line
}
return s;
}
int main()
{
cout << "Input something: ";
string s = getInput();
cout << "Your input: " << s << "\n" << endl;
for(int i=0; i<words.size(); i++){
cout << words[i] << "\n";
}
}
The code puts strings into a vector but takes the last word of the sentence and attaches it to the next string and I cannot seem to understand why.
This line
s = s + " " + l;
will always execute, except for the end of input, even if the last character is '.'. You are most likely missing an else between the two if-s.
You have:
string l; // string to hold a line
cin >> l; // get line
The last line does not read a line unless the entire line has non-white space characters. To read a line of text, use:
std::getline(std::cin, l);
It's hard telling whether that is tripping your code up since you haven't posted any sample input.
I would at least consider doing this job somewhat differently. Right now, you're reading a word at a time, then putting the words back together until you get to a period.
One possible alternative would be to use std::getline to read input until you get to a period, and put the whole string into the vector at once. Code to do the job this way could look something like this:
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <iterator>
int main() {
std::vector<std::string> s;
std::string temp;
while (std::getline(std::cin, temp, '.'))
s.push_back(temp);
std::transform(s.begin(), s.end(),
std::ostream_iterator<std::string>(std::cout, ".\n"),
[](std::string const &s) { return s.substr(s.find_first_not_of(" \t\n")); });
}
This does behave differently in one circumstance--if you have a period somewhere other than at the end of a word, the original code will ignore that period (won't treat it as the end of a sentence) but this will. The obvious place this would make a difference would be if the input contained a number with a decimal point (e.g., 1.234), which this would break at the decimal point, so it would treat the 1 as the end of one sentence, and the 234 as the beginning of another. If, however, you don't need to deal with that type of input, this can simplify the code considerably.
If the sentences might contain decimal points, then I'd probably write the code more like this:
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <iterator>
class sentence {
std::string data;
public:
friend std::istream &operator>>(std::istream &is, sentence &s) {
std::string temp, word;
while (is >> word) {
temp += word + ' ';
if (word.back() == '.')
break;
}
s.data = temp;
return is;
}
operator std::string() const { return data; }
};
int main() {
std::copy(std::istream_iterator<sentence>(std::cin),
std::istream_iterator<sentence>(),
std::ostream_iterator<std::string>(std::cout, "\n"));
}
Although somewhat longer and more complex, at least to me it still seems (considerably) simpler than the code in the question. I guess it's different in one way--it detects the end of the input by...detecting the end of the input, rather than depending on the input to contain a special delimiter to mark the end of the input. If you're running it interactively, you'll typically need to use a special key combination to signal the end of input (e.g., Ctrl+D on Linux/Unix, or F6 on Windows).
In any case, it's probably worth considering a fundamental difference between this code and the code in the question: this defines a sentence as a type, where the original code just leaves everything as strings, and manipulates strings. This defines an operator>> for a sentence, that reads a sentence from a stream as we want it read. This gives us a type we can manipulate as an object. Since it's like a string in other ways, we provide a conversion to string so once you're done reading one from a stream, you can just treat it as a string. Having done that, we can (for example) use a standard algorithm to read sentences from standard input, and write them to standard output, with a new-line after each to separate them.

How to change each word in a string vector to upper case

I was inquiring about reading a sequence of words and storing the values in a vector. Then proceed to change each word in the vector to uppercase and print the out put with respect to eight word to a line. I think my code is either slow or running infinitely as i can't seem to achieve an output.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
string word;
vector<string> text;
while (getline(cin, word)) {
text.push_back(word);
}
for (auto index = text.begin(); index != text.end(); ++index) {
for ( auto it = word.begin(); it != word.end(); ++it)
*it = toupper(*it);
/*cout<< index << " " << endl;*/
}
for (decltype(text.size()) i = 0; i != 8; i++)
cout << text[i] << endl;
return 0;
}
At least as far as I can tell, the idea here is to ignore the existing line structure, and write out 8 words per line, regardless of line breaks in the input data. Assuming that's correct, I'd start by just reading words from the input, paying no attention to the existing line breaks.
From there, it's a matter of capitalizing the words, writing them out, and (if you're at a multiple of 8, a new-line.
I would also use standard algorithms for most of the work, instead of writing my own loops to do the pars such as reading and writing the data. Since the pattern is basically just reading a word, modifying it, then writing out the result, it fits nicely with the std::transform algorithm.
Code to do that could look something like this:
#include <string>
#include <iostream>
#include <algorithm>
std::string to_upper(std::string in) {
for (auto &ch : in)
ch = toupper((unsigned char) ch);
return in;
}
int main() {
int count = 0;
std::transform(
std::istream_iterator<std::string>(std::cin),
std::istream_iterator<std::string>(),
std::ostream_iterator<std::string>(std::cout),
[&](std::string const &in) {
char sep = (++count % 8 == 0) ? '\n' : ' ';
return to_upper(in) + sep;
});
}
We could implement capitalizing each string as a second lambda, nested inside the first, but IMO, that starts to become a bit unreadable. Likewise, we could use std::tranform to implement the upper-case transformation inside of to_upper.
I'll rewrite my answer here:
Your outer for loop defines index to cycle through text, but you never use index inside it. The inner loop uses word, but word is still the last one the user entered. You should change the inner loop so that it uses index instead of word, like this:
for ( auto it = index->begin(); it != index->end(); ++it)
This is effectively an infinite loop:
while (getline(cin, word)) {
text.push_back(word);
}
getline(cin, word) reads a line (ending in '\n') from stdin, and puts it into word. It then returns cin itself (which will evaluate to true if the read was successful). You seem to be using it to get a space-delimited word, rather than a whole line, but that's not what it does. Since you put it in the condition of the while, after you enter a line, it will wait for another line.
This loop only breaks when getline fails. For example, by hitting an End of File character. I expect you're using the console and pressing Enter. In that case, you are never causing getline to fail. (If you're feeding a file into stdin, it should work.)
The typical solution to this is to have some sort of way of indicating a stop (such as an "Enter an empty line to stop" or "Write \"STOP\" to stop", and then checking for that before inserting the line into the vector). For you, the solution is to read in a SINGLE line, and then break it up into words (for example, using the sstream library).
You can detect whether the program is doing actual work (rather than waiting for more input) by viewing your CPU use. In Windows, this is CTRL+SHIFT+ESC -> Performance, and -> Processes to see your program in particular. You will find that the program isn't actually using the CPU (because it's waiting for more input).
You should try inserting print statements into your program to figure out where it gets up to. You will find it never goes past the for-loop.
Short Answer
for (string &str : vec)
{
transform(str.begin(), str.end(), str.begin(), [](char c) { return std::toupper(c); });
}
Complete working code as example:
#include <iostream>
#include <string>
#include <vector>
#include <cctype>
#include <algorithm>
using namespace std;
int main()
{
vector<string> vec;
string str;
while (cin >> str)
{
vec.push_back(str);
}
for (string &str : vec)
{
transform(str.begin(), str.end(), str.begin(), [](char c)
{ return toupper(c); });
}
for (auto str : vec)
{
cout << str << endl;
}
return 0;
}

C++ Remove punctuation from String

I got a string and I want to remove all the punctuations from it. How do I do that? I did some research and found that people use the ispunct() function (I tried that), but I cant seem to get it to work in my code. Anyone got any ideas?
#include <string>
int main() {
string text = "this. is my string. it's here."
if (ispunct(text))
text.erase();
return 0;
}
Using algorithm remove_copy_if :-
string text,result;
std::remove_copy_if(text.begin(), text.end(),
std::back_inserter(result), //Store output
std::ptr_fun<int, int>(&std::ispunct)
);
POW already has a good answer if you need the result as a new string. This answer is how to handle it if you want an in-place update.
The first part of the recipe is std::remove_if, which can remove the punctuation efficiently, packing all the non-punctuation as it goes.
std::remove_if (text.begin (), text.end (), ispunct)
Unfortunately, std::remove_if doesn't shrink the string to the new size. It can't because it has no access to the container itself. Therefore, there's junk characters left in the string after the packed result.
To handle this, std::remove_if returns an iterator that indicates the part of the string that's still needed. This can be used with strings erase method, leading to the following idiom...
text.erase (std::remove_if (text.begin (), text.end (), ispunct), text.end ());
I call this an idiom because it's a common technique that works in many situations. Other types than string provide suitable erase methods, and std::remove (and probably some other algorithm library functions I've forgotten for the moment) take this approach of closing the gaps for items they remove, but leaving the container-resizing to the caller.
#include <string>
#include <iostream>
#include <cctype>
int main() {
std::string text = "this. is my string. it's here.";
for (int i = 0, len = text.size(); i < len; i++)
{
if (ispunct(text[i]))
{
text.erase(i--, 1);
len = text.size();
}
}
std::cout << text;
return 0;
}
Output
this is my string its here
When you delete a character, the size of the string changes. It has to be updated whenever deletion occurs. And, you deleted the current character, so the next character becomes the current character. If you don't decrement the loop counter, the character next to the punctuation character will not be checked.
ispunct takes a char value not a string.
you can do like
for (auto c : string)
if (ispunct(c)) text.erase(text.find_first_of(c));
This will work but it is a slow algorithm.
Pretty good answer by Steve314.
I would like to add a small change :
text.erase (std::remove_if (text.begin (), text.end (), ::ispunct), text.end ());
Adding the :: before the function ispunct takes care of overloading .
The problem here is that ispunct() takes one argument being a character, while you are trying to send a string. You should loop over the elements of the string and erase each character if it is a punctuation like here:
for(size_t i = 0; i<text.length(); ++i)
if(ispunct(text[i]))
text.erase(i--, 1);
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string str = "this. is my string. it's here.";
transform(str.begin(), str.end(), str.begin(), [](char ch)
{
if( ispunct(ch) )
return '\0';
return ch;
});
}
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;//string is defined here.
cout << "Please enter a string with punctuation's: " << endl;//Asking for users input
getline(cin, s);//reads in a single string one line at a time
/* ERROR Check: The loop didn't run at first because a semi-colon was placed at the end
of the statement. Remember not to add it for loops. */
for(auto &c : s) //loop checks every character
{
if (ispunct(c)) //to see if its a punctuation
{
c=' '; //if so it replaces it with a blank space.(delete)
}
}
cout << s << endl;
system("pause");
return 0;
}
Another way you could do this would be as follows:
#include <ctype.h> //needed for ispunct()
string onlyLetters(string str){
string retStr = "";
for(int i = 0; i < str.length(); i++){
if(!ispunct(str[i])){
retStr += str[i];
}
}
return retStr;
This ends up creating a new string instead of actually erasing the characters from the old string, but it is a little easier to wrap your head around than using some of the more complex built in functions.
I tried to apply #Steve314's answer but couldn't get it to work until I came across this note here on cppreference.com:
Notes
Like all other functions from <cctype>, the behavior of std::ispunct
is undefined if the argument's value is neither representable as
unsigned char nor equal to EOF. To use these functions safely with
plain chars (or signed chars), the argument should first be converted
to unsigned char.
By studying the example it provides, I am able to make it work like this:
#include <string>
#include <iostream>
#include <cctype>
#include <algorithm>
int main()
{
std::string text = "this. is my string. it's here.";
std::string result;
text.erase(std::remove_if(text.begin(),
text.end(),
[](unsigned char c) { return std::ispunct(c); }),
text.end());
std::cout << text << std::endl;
}
Try to use this one, it will remove all the punctuation on the string in the text file oky.
str.erase(remove_if(str.begin(), str.end(), ::ispunct), str.end());
please reply if helpful
i got it.
size_t found = text.find('.');
text.erase(found, 1);

How do I split a user-defined sentence into words in C++ using substr and find?

I used this function but it is wrong.
for (int i=0; i<sen.length(); i++) {
if (sen.find (' ') != string::npos) {
string new = sen.substr(0,i);
}
cout << "Substrings:" << new << endl;
}
Thank you! Any kind of help is appreciated!
new is a keyword in C++, so first step is to not use that as a variable name.
After that, you need to put your output statement in the "if" block, so that it can actually be allowed to access the substring. Scoping is critical in C++.
First: this cannot compile because new is a language keyword.
Then you have a loop running through every character in the string so you shouldn't need to use std::string::find. I would use std::string::find, but then the loop condition should be different.
This doesn't use substr and find, so if this is homework and you have to use that then this won't be a good answer... but I do believe it's the better way to do what you're asking in C++. It's untested but should work fine.
//Create stringstream and insert your whole sentence into it.
std::stringstream ss;
ss << sen;
//Read out words one by one into a string - stringstream will tokenize them
//by the ASCII space character for you.
std::string myWord;
while (ss >> myWord)
std::cout << myWord << std::endl; //You can save it however you like here.
If it is homework you should tag it as such so people stick to the assignment and know how much to help and/or not help you so they don't give it away :)
No need to iterate over the string, find already does this. It starts to search from the beginning by default, so once we found a space, we need to start the next search from this found space:
std::vector<std::string> words;
//find first space
size_t start = 0, end = sen.find(' ');
//as long as there are spaces
while(end != std::string::npos)
{
//get word
words.push_back(sen.substr(start, end-start));
//search next space (of course only after already found space)
start = end + 1;
end = sen.find(' ', start);
}
//last word
words.push_back(sen.substr(start));
Of course this doesn't handle duplicate spaces, starting or trailing spaces and other special cases. You would actually be better off using a stringstream:
#include <sstream>
#include <algorithm>
#include <iterator>
std::istringstream stream(sen);
std::vector<std::string> words(std::istream_iterator<std::string>(stream),
std::istream_iterator<std::string>());
You can then just put these out however you like or just do it directly in the loops without using a vector:
for(std::vector<std::string>::const_iterator iter=
words.begin(); iter!=words.end(); ++iter)
std::cout << "found word: " << *iter << '\n';