Use stringstream to read line of varying elements - c++

I have a file I need to read in that looks somethig like this:
1 2.23 Dove Body Wash
3 .50 Bic Pen
11 12.99 Tombstone Pizza
Where the field with the names of the products can have either only one word (Shampoo) or any number of words (Big mama's homestyle steak fries). The input file can also have blank lines, which I just need to skip over.
So what I have right now looks like this (using getline and stringstream):
This struct:
struct CartItem {
string itemName;
int quantity;
double pricePerItem;
CartItem();
CartItem(string name, int qty,double price);
};
and then this code.
while (getline(itemList, inputline)) { //itemlist is my ifstream, inputline is a string declared.
ss.clear();
ss.str(inputline);
ss >> item.quantity >> item.pricePerItem >> item.itemName;
string word;
while (ss >> word)
{
item.itemName += " " + word;
}
if (ss.fail()) {
continue;
}
else
shoppingList.push_back(item);
}
sortItems(shoppingList, sortedList);
printReport(sortedList);
but it's not working (just crashing). If I replace the while(ss >> word) fragment with if(ss>>word) it works but I only get the second string from the file. Can anyone see what I'm doing wrong here?

You should provide more details, e.g. what "just crashing" means. However, I think you have problem with your logic.
ss >> word will be called on the final check in the while loop, when ss is already out of words, thus the word will fail to be extracted to word when the while loop exits, setting ss's fail bit. Now, ss.fail() will always be true and you will never add items to your list.
I am guessing that you later rely on shoppingList having at least one item and this is the crash.
Try changing:
while (ss >> word)
{
item.itemName += " " + word;
}
to:
while (ss.good())
{
ss >> word;
item.itemName += " " + word;
}

Related

C++ getline() not stopping at the end of line?

I'm sure there's a newbie mistake here, but I can't for the life of me figure it out.
I'm trying to use getline() to read a two word line (first and last names), and then sort the names into member fields of a struct. However, getline() seems to be rolling past the end of the lines and appending the first word of the next line to the last word of the line it should be getting.
In other words, when trying to read the last word of a line, getline() is reading that word and the first word of the next line.
The input file is of the form:
Seattle Mariners
Norichiki Aoki
Seth Smith
Robinson Cano
Here's my code:
struct Player {
string firstName;
string lastName;
float avg;
};
struct Team {
Player roster[8];
string teamName;
};
Team home = {};
stringstream iss;
string token;
string lineread;
while (getline(inFile, lineRead, '\n')){
iss << lineRead;
if (getline(iss, token, ' '))
{
if(s % 2 == 0)
home.roster[s/2].firstName = lineRead;
else
home.roster[s/2].lastName = lineRead;
}
s++;
cout << "token:" << token << endl;
}
The output I'm getting looks like this:
token: Seattle
token: MarinersNorichiki
token: AokiSeth
token: SmithRobinson
But I would like to have
token: Seattle
token: Mariners
token: Norichiki
token: Aoki
std::cin.getline() can run into problems when used with std::cin >> var.
getline can be provided a third argument--a "stop" character. This character ends getline's input. The character is eaten and the string is terminated. Example:
std::cin.getline(str, 100, '|')
If std::cin.getline() is not provided a "stop" character as a third argument, it will stop when it reaches a newline.
Given:
float fl;
std::cin >> fl;
char str[101]
std::cin.getline(str, 101);
And you type: 3.14
3.14 is read into fl . The newline following the 3.14 is still sitting on the input buffer.
std::cin.getline(str, 101) immediately processes the newline that is still on the input buffer. str becomes an empty string.
The illusion is that the application "skipped" the std::cin.getline() statement.
The solution is to add std::cin.ignore(); immediately after the first std::cin statement. This will grab a character off of the input buffer (in this case, newline) and discard it.
std::cin.ignore() can be called three different ways:
No arguments: A single character is taken from the input buffer and discarded:
std::cin.ignore(); //discard 1 character
One argument: The number of characters specified are taken from the input buffer and discarded:
std::cin.ignore(33); //discard 33 characters
Two arguments: discard the number of characters specified, or discard characters up to and including the specified delimiter (whichever comes first):
std::cin.ignore(26, '\n'); //ignore 26 characters or to a newline, whichever comes first
Try something more like this instead:
struct Player {
string firstName;
string lastName;
float avg;
};
struct Team {
Player roster[8];
string teamName;
};
Team home = {};
int s = 0;
string line;
while (getline(inFile, line))
{
istringstream iss(line);
iss >> home.roster[s].firstName;
iss >> home.roster[s].lastName;
home.roster[s].avg = ...;
cout << "first: " << home.roster[s].firstName << ", last: " << home.roster[s].lastName << endl;
if (++s == 8) break;
}

Simple casting conversion on C++

I'm doing an exercise for the college and I have to compare a string added including the header <string>, and a character.
I have a text file with a few lines of data from a census, like
Alabama AL 4849377 Alaska AK 736732 Arizona AZ 6731484
I want to read the state name of each line with a string variable, but the comparison is the only thing that I am asking for, because is where I have the error.
I have this fragment of code:
struct Census{
string name;
int population, code;
};
struct States{
Census state;
};
typedef States Vector[US_STATES];
void loadCensus(ifstream & census, Vector stats){
int i=0;
string readData;
string line;
while (getline(census, line)) {
stringstream linestream(line);
while (linestream >> readData) {
if (linestream >> stats[i].state.name >>
stats[i].state.code >>
stats[i].state.population)
{
std::cerr << "Bad input on line " << i << ": " << line << std::endl;
}
stats[i].state.name=readData;
stats[i].state.code=readData;
stats[i].state.population=readData;
i++;
}
}
}
How I should convert readData to an integer to assign stats[i].state.population=readData?
I get an error in line 17 in the linestream >> readData.
You want to use the getline() function instead.
I think ita a member function of ifstream or either compare the not readData to a string ("\n") - double quotation. Or put the read data into a string and check if the sting contains a '\n'.
census >> readData will read the next word (any group of non-whitespace characters) from the input. In order to do this, it will discard all whitespace on its hunt for the next word. '\n' is whitespace, so you will never read it with the >> operator without playing games you probably don't want to play.
Instead of >>, use std::getline to read a line and then use a std::stringstream to break the line up into words.
std::string line;
while (std::getline(census, line)) {
std::stringgstream linestream(line);
while (linestream >> readData) {
statistics.state[i]=readData;
i++;
}
}
But...
I do not believe statistics.state[i]=readData; does quite what you want to do. You probably want something more like:
std::string line;
while (std::getline(census, line)) {
std::stringstream linestream(line);
if (!(linestream >> statistics.state[i].name >>
statistics.state[i].abbreviation >>
statistics.state[i].population))
{
std::cerr << "Bad input on line " << i << ": " << line << std::endl;
}
i++;
}
In this state becomes an array or vector of objects that probably looks something like
struct statestats
{
std::string name;
std::string abbreviation;
int population;
};
Breaking it down line by line
std::stringstream linestream(line);
Makes a stringstream. A string stream is a stream like cin and cout or a fstream, but it contains a string. The main use is to buffer and build strings with the same syntax you would use on another stream. In this case we are use it to split up the line into words.
if (linestream >> statistics.state[i].name >>
statistics.state[i].abbreviation >>
statistics.state[i].population)
Needs to be handled in a few parts in a few parts. Over all it is an abbreviation of
if (linestream >> statistics.state[i].name &&
linestream >> statistics.state[i].abbreviation &&
linestream >> statistics.state[i].population)
Each stage of which reads from the linestream into a variable.
Next, the >> operator returns the stream being read, and this is used two ways in the example. The first allows chaining. The output of one >> is used as the input of the next, so if you look at >> as you would a function (and it is a function. See Stream extraction and insertion for more) you can think about it looking something like this:
linestream.read(statistics.state[i].name).read(statistics.state[i].abbreviation).read(statistics.state[i].population)
The >> syntax just makes it easier.
The next advantage you get from returning the stream is the stream can be tested to see if the stream is still good. It has a boolean operator that will return true if the stream is in a good state and can be used.
if(linestream)
{
good
}
else
{
bad
}
will enter good if the stream is open, has not reached the end of the stream, and has had no troubles reading or writing data.
Going back to our example
if (linestream >> statistics.state[i].name >>
statistics.state[i].abbreviation >>
statistics.state[i].population)
Will enter the body of the if statement if the stream successfully read all three values from the stream. Which is not what we want. Ooops. I've corrected the above code already.
if (!(linestream >> statistics.state[i].name >>
statistics.state[i].abbreviation >>
statistics.state[i].population))
will enter the body of the if if at least one value was not read for any reason and print out an error message. Normally when there is an error you will need to clear the error before continuing, but in this case we've use the whole stream and are about to discard it.
Assuming no error occurred all of the data from this line has been read and there is no need to
stats[i].state.name=readData;
stats[i].state.code=readData;
stats[i].state.population=readData;

istringstream not outputting correct data

I am having trouble getting istringstream to continue in while loop shown below. The data file is shown below also. I use getline from Input file to get the first line and put it in a istringstream lineStream. It passes through the while loop once, then it reads in the second line and goes back to the beginning of the loop and exits rather than continue through the loop. I have no clue why, if anyone could help I would thankful.
EDIT: The reason I have this while loop condition is because the file may contain lines of erroneous data. Therefore, I want to make sure the line I am reading in has the proper form shown below in the data file.
while(lineStream >> id >> safety){//keeps scanning in xsections until there is no more xsection IDs
while(lineStream >> concname){//scan in name of xsection
xname = xname + " " +concname;
}
getline(InputFile, inputline);//go to next xsection line
if(InputFile.good()){
//make inputline into istringstream
istringstream lineStream(inputline);
if(lineStream.fail()){
return false;
}
}
}
Data FILE
4 0.2 speedway and mountain
7 0.4 mountain and lee
6 0.5 mountain and santa
In the presented code, …
while(lineStream >> id >> safety){//keeps scanning in xsections until there is no more xsection IDs
while(lineStream >> concname){//scan in name of xsection
xname = xname + " " +concname;
}
getline(InputFile, inputline);//go to next xsection line
if(InputFile.good()){
//make inputline into istringstream
istringstream lineStream(inputline);
if(lineStream.fail()){
return false;
}
}
}
… the inner declaration of lineStream declares a local object, which ceases to exist when the execution passes out of that block, and which doesn't affect the stream used in the outer loop.
One possible fix is to invert the code a little bit, like this:
while( getline(InputFile, inputline) )
{
istringstream lineStream(inputline);
if(lineStream >> id >> safety)
{
while(lineStream >> concname)
{
xname = xname + " " +concname;
}
// Do something with the collected info for this line
}
}

Reading a sequence of words to add them in a vector

I recently bought a C++ Primer and got stuck with a problem. I have to read a sequence of words using cin and store the values in a vector. After having unusual problems, I found out that while(cin >> words) invites problems (like infinite loop) if you expect invalid inputs: Using cin to get user input
int main()
{
string words;
vector<string> v;
cout << "Enter words" << endl;
while (cin >> words)
{
v.push_back(words);
}
for(auto b : v)
cout << b << " ";
cout << endl;
return 0;
}
Therefore, I'm trying to find an alternative to this problem. Help ?
That link you provided regarding input problems is a little different. It's talking about when you expect the user to enter a particular value, but you might fail to read the value (let's say it's an integer) because something else was entered. In that case, it's good to use getline to retrieve a whole line of input and then parse the value out.
In your case, you're just after words. When you read a string from a stream, it will give you all consecutive non-whitespace characters. And, ignoring punctuation for a moment, you can call that a "word". So when you talk about 'invalid input', I don't see what you mean. The loop will continue to give you "words" until there are none left in the stream, at which point it will error:
vector<string> words;
string word;
while( cin >> word ) words.push_back(word);
However, if you expect the user to enter all words on one line and press enter to finish, then you need to use getline:
// Get all words on one line
cout << "Enter words: " << flush;
string allwords;
getline( cin, allwords );
// Parse words into a vector
vector<string> words;
string word;
istringstream iss(allwords);
while( iss >> word ) words.push_back(word);
Or you can do this:
cout << "Enter words, one per line (leave an empty line when done)\n";
vector<string> words;
string line;
while( getline(cin, line) )
{
// Because of the word check that follows, you don't really need this...
if( line.size() == 0 ) break;
// Make sure it's actually a word.
istringstream iss(line);
string word;
if( !(iss >> word) ) break;
// If you want, you can check the characters and complain about non-alphabet
// characters here... But that's up to you.
// Add word to vector
words.push_back(word);
}

input from txt file to arrays

I have a text file with a line like:
James Dean 10 Automotive 27010.43
and I need to read that file and put each of the 4 above into arrays.
char nameArray[MAX][NAME_MAX];
int yearArray[MAX];
char departmentArray[MAX][DEP_MAX];
double payArray[MAX];
while(i < MAX && infile) {
infile.getline(nameArray[i], 20);
infile >> yearArray[i];
infile.getline(departmentArray[i], 15);
infile >> payArray[i];
cout << nameArray[i] << " " << yearArray[i] << " " << departmentArray[i] << " " << fixed << setprecision(2) << payArray[i] << endl;
i++;
}
The code is cut down just to give you an idea of what I am trying to do, but when I run this, I get something like:
James Dean -858993460 -92559631349317830000000000000000000000000000
000000000000000000.00
Thanks for the help.
==== Edit ==========================================
I changed from getline to get, thanks for that. I have to use get and not >> because some of the lines I am reading in are more than just "James Dean", they are up to 20 char long...ex: "William K. Woodward" is another one.
So, if I just use get, then it reads the first line in fine, but then I get the same messed up text for the second line.
Here is the code:
infile.get(nameArray[i], 20);
infile >> yearArray[i];
infile.get(departmentArray[i], 15);
infile >> payArray[i];
The getline functions takes an input stream and a string to write to. So, two getline calls read in two lines. Your input mechanism is broken. Either, use getline or the stream extraction operator (i.e. >>) but not both.
If you plan to use getline you need to parse the string (which is effectively one line of input) into tokes, and then store them in appropriately typed arrays. The second and fourth tokens are numbers, hence you will need to convert these from string to int or double.
The operator >> approach:
string name, surname;
int year;
double pay;
while (infile) {
infile >> name >> surname >> year >> department >> pay;
namearray[ i ] = name + " " + surname;
// ...
payarray[ i ] = pay;
++i;
}
The getline approach:
string line;
while (getline(infile, line)) {
parse(line, tokens);
namearray[ i ] = token[ 0 ] + " " + token[ 1 ];
// ...
payarray[ i ] = strTodouble(token[ 4 ]);
++i;
}
// parse definition
void parse(string line, vector<string>& token) {
// roll your own
}
double strToDouble(string s) {
// ...
}
I dont see where you define infile but I will assume that it is an ifile . In that case you should use it the same way u use cin to get input.
Why do you do a getline () ?
That function will stop only at an '\n' char or at an EOF char. So it means, you start reading the int after the end of the line, some random data.
Correct me if i'm wrong, but are there 20 or 19 characters in that first string (James Dean) before the number (10) ?