This question already has answers here:
Why does std::getline() skip input after a formatted extraction?
(5 answers)
Closed 1 year ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
For context, I am working on a reward system program for a store. I have a file which contains a list of items with the points the customer earns underneath the name of each item. Here is what the file contains:
rolling papers
1
lighter
1
silicone pipe
5
glass pipe
8
water pipe
10
I am trying to read from the file into two different variables then store those variables in a vector of pairs. But I've noticed that when I output the pairs from the vector the first letter of the item is missing, and the points are completely off. I've tried to change the point to a char instead of an int, and did the same in the paired vector. Both gave similarly inaccurate/strange outputs. What am I doing wrong here? Thanks guys. Here is the part of my program where I'm trying to store the items/points in a paired vector:
int answer;
int points;
std::string tempName;
std::string name;
std::string item;
std::ifstream inFS;
std::vector<std::string> nameList;
std::vector<std::pair<std::string, int>> pairedList;
std::cout << "Would you like to add points to a member's name? If not, input 0 to look at other options!" << std::endl;
std::cout<< "Otherwise, input 1 to continue to the point system." << std::endl;
std::cin >> answer;
if (answer == 0)
options();
if (answer == 1) {
inFS.open("items.dat");
if (inFS.is_open())
std::cout << "File opened successfully." << std::endl;
while (std::getline(inFS, item)) {
inFS >> points;
pairedList.push_back(make_pair(item, points));
}
if (!inFS.eof())
std::cout << "Not able to reach end of file" << std::endl;
inFS.close();
for (int i = 0; i < pairedList.size(); i++)
std::cout << pairedList[i].first << " " << pairedList[i].second << std::endl;
exit(1);
}
}
Try changing the inFS >> points for another std::getline(inFS, points_str) (notice you need a std::string points_str{};); then you can do a make_pair(item, std::stoi(points_str)). [Demo]
#include <iostream> // cout
#include <string> // getline, stoi
int main()
{
std::string item{};
std::string points{};
while (std::getline(std::cin, item))
{
std::getline(std::cin, points);
std::cout << "item = " << item << ", points = " << std::stoi(points) << "\n";
}
}
Related
While creating a simple stock management system, I encountered some problem after adding a new item in the list. My code will explain better.
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std; // For sake of speed
class INVENTORY
{
char name[10];
int code;
float cost;
public:
void setData();
void getData();
};
void INVENTORY ::setData()
{
std::cout << "Enter Name: ";
std::cin.getline(name, 10);
std::cout << "Enter Code: ";
std::cin >> code;
std::cout << "Enter Cost: ";
std::cin >> cost;
}
void INVENTORY::getData()
{
cout << setiosflags(ios::left) << setw(10) << name << setw(10) << code << setw(5) << cost << '\n';
}
int main()
{
INVENTORY item;
fstream inoutfile;
inoutfile.open("STOCK.DAT", ios::ate | ios::in | ios::out | ios::binary);
inoutfile.seekg(0, ios::beg); // go to beginning.
cout << "CURRENT CONTENTS OF STOCK" << '\n';
while (inoutfile.read((char *)&item, sizeof(item)))
{
item.getData();
}
inoutfile.clear(); // clears the eof state flag.
cout<<'\n'<<inoutfile.good()<<'\n';
/* ADD more items */
cout << "\nAdd an item: \n";
item.setData();
char ch;
cin.get(ch);
inoutfile.write((char *)&item, sizeof(item));
cout<<'\n'<<inoutfile.good()<<'\n';
// Display the appended file
inoutfile.seekg(0);
cout << "CONTENTS OF APPENDED FILE\n";
while (inoutfile.read((char *)&item, sizeof(item)))
{
item.getData();
}
cout<<'\n'<<inoutfile.good()<<'\n';
// Find the number of objects in the file.
int last = inoutfile.tellg();
int n = last / sizeof(item);
cout << "Number of Objects: " << n << endl;
cout << "Total bytes in file: " << last << endl;
/* Modify the details of an item */
cout << "Enter object number to be updated: ";
int object;
cin >> object;
cin.get(ch);
int location = (object - 1) * sizeof(item);
if (inoutfile.eof())
{
inoutfile.clear();
}
inoutfile.seekp(location);
cout << "Enter the new values of objects \n";
item.setData();
cin.get(ch);
inoutfile.write((char *)&item, sizeof item) << flush;
/* SHOW UPDATED FILE */
cout << "Contents of updated file: \n";
while (inoutfile.read((char *)&item, sizeof item))
{
item.getData();
}
inoutfile.close();
return 0;
}
I reused the class from some file, please don't sue me for using namespace std I don't use it usually, but used today for sake of speed.
The 3rd cout<<'\n'<<inoutfile.good()<<'\n'; returns false and I am not able to figure out why this happens. I already have the file STOCK.DAT and there is data already present in it (of the same type). The relevant output:
CURRENT CONTENTS OF STOCK
Apple 5 50
Banana 6 80
1
Add an item:
Enter Name: Pineapple
Enter Code: 8
Enter Cost: 150
1
CONTENTS OF APPENDED FILE
Apple 5 50
Banana 6 80
Pineapple 8 150
0 // something is not good here but what?
Number of Objects: -858993460
Total bytes in file: -1
There are more elements in output but I showed you the relevant output, tellg returns false, so there is definitely something wrong and I can't figure it out.
I will explain what is happening over here:
I made a class named INVENTORY and made a few members and member functions in it.
Then in the main function, I made a fstream object and opened a file named STOCK.DAT, with a few additional flags.
Then I point the get pointer to the beginning (both pointers move together for a fstream object).
The file is in binary so I print out what's already present in the file.
Then I use clear() to remove eof flag and check whether everything is right, which will result true here.
Then I add another item to the end of file and check whether everything is good, true over here also.
Then I set the get pointer to beginning to print all the data in the file again and now everything is not good and I am not able to figure it out.
I found a solution. After reaching eof if you try to do inoutfile.tellg() it will return -1. Instead use inoutfile.clear() to clear the eof tag and then use inoutfile.tellg().
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
This is what I tried. I tried strcpy, substituting the value and using in_file again 3 of them can't work. This program is supposed to use the data in a input file and then does functions 1-4 selected by user.
List all products
Search the price of a product
Update the price of a product
Exit
If the user chooses to exit, the program will write the updated data in the array of structures to the file.
The product information includes product number, description and price.
#include <iomanip>
#include <fstream>
#include <cstring>
using namespace std;
typedef struct
{
char product_num[5];
char product_des[29];
float price;
}PRODUCT_TYPE;
int main()
{
ifstream in_file("input practical 12 part d.txt");
if (!in_file)
{
cout << "Error opening input file" << endl;
}
else
{
PRODUCT_TYPE product[50];
int index = -1;
int choice;
in_file >> product[++index].product_num;
cout << "Succesful run" << endl;
cout << "Menu (Type number for the function)" << endl;
cout << "1. List all products\n2.Search the price of a product\n3.Update the price of a product\n4.Exit\n";
cin >> choice;
if (choice == 1)
{
cout << setw(18) << left << "Product Index" << setw(35) << left << "Product Description" << "Price" << endl;
}
else if (choice == 3)
{
cout << "Enter the product number to change its price: ";
cin >> product[index].product_num;
cout << "Enter the new price of the product: ";
cin >> product[index].price;
}
else if (choice == 4)
{
cout << setw(18) << left << "Product Index" << setw(35) << left << "Product Description" << "Price" << endl;
}
while (in_file)
{
in_file >> product[index].product_des >> product[index].price;
if (choice == 1)
{
cout << setw(18) << left << product[index].product_num << setw(35) << left << product[index].product_des << product[index].price << endl;
}
else if (choice == 2)
{
cout << "Type the product number to get the price" << endl;
cout << "Product Number: ";
cin >> product[index].product_num;
cout << "Price: ";
cout << product[index].price << endl;
}
else if (choice == 4)
{
cout << setw(18) << left << product[index].product_num << setw(35) << left << product[index].product_des << product[index].price << endl;
}
in_file >> product[++index].product_num;
}
in_file.close();
out_file.close();
}
return 0;
}```
Unfortunately changes in existing files are not that easy. The problem is that data are stored byte after byte in a file on a disk (sometimes even not that). So, if you have data in your file (always one byte) like the below
Position in file: 0 1 2 3 4 5 9 7 8 9 . . .
Bytes as decimal: . . . 49 50 51 61 62 63 . . .
Bytes as character: . . . 1 2 3 A B C . . .
And you want to update 1 2 3 at position 3 by 1 2 3 4 5 then you would need to shift all folowing bytes in the file by 2 characters, because 12345 is 2 bytes longer than 123. Nobody can do that. And it will not be done automatically.
So, there are basically 2 approaches to solve such problems.
Open the source file -> Read all data into memory -> Close source file -> Do the modifications in memory -> Open source file in overwrite mode -> Write new data.
Use a temporary file. -> Open the source file for input -> Open a temporary file for output -> Read source file line by line -> Do modifications for each read line immediately -> Write result to temp file -> After all source file data have been read, close both files -> Delete the original source file. Rename the tempfile to the old source file name.
(3. If the length of the data, that you want to modifiy, is the same than the old data, then you could move the file write pointer and overwrite the old data. But you need always the same length. So, most often not feasible)
There are of course more solutions, but those 2 approaches are often used.
Please see some pure example codes. Just plain example, unrelated to your code. No productive code. No error checking. Just to give you an idea, how it could work.
Method 1
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
std::vector<std::string> readFile(const std::string& filename) {
// Here we will store all the data from the file
std::vector<std::string> fileData;
// Open the source file
std::ifstream fileStream(filename);
// Read line by line and add it to our fileData
std::string line;
while (std::getline(fileStream, line)) {
fileData.push_back(line);
}
return fileData;
}
void writeFile(std::vector<std::string>& fileData, const std::string& filename) {
// Open file for output
std::ofstream fileStream(filename);
// Write all data to file
for (const std::string& line : fileData)
fileStream << line << '\n';
}
int main() {
// Aproach with read complete file to local variable, modify and the store again
const std::string dataFileName("r:\\test.txt");
// Get file content
std::vector<std::string> data = readFile(dataFileName);
// Now go through all records and do something
for (std::string& line : data) {
// If some condition is met then do something, for example modify
if (line == "Line1") line += " modified";
}
// And then write the new data to the file
writeFile(data, dataFileName);
return 0;
}
Method 2:
#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
int main() {
// Aproach with temp file, remove and rename, and on the fly change
const std::string dataFileName("r:\\test.txt");
const std::string tempFileName("r:\\temp.txt");
{
// Open the source file with data
std::ifstream dataFileStream(dataFileName);
// Open the temporary file for output
std::ofstream tempFileStream(tempFileName);
// Now read the source file line by line with a simple for loop
std::string line;
while (std::getline(dataFileStream, line)) {
// Identify the line that should be deleted and do NOT write it to the temp file
if (line != "SearchString") { // Or any other condition
// Write only, if the condition is not met
tempFileStream << line << '\n';
}
}
} // The end of the scope for the streams, will call their destructor and close the files
// Now, remove and rename
std::remove(dataFileName.c_str());
std::rename(tempFileName.c_str(), dataFileName.c_str());
return 0;
}
I wrote a code which shows a question and 4 answers, ask to choose the correct one, and checks the choice.
Now I would build a table which would contain 6 columns: question/a/b/c/d/correct_answer, and about hundred of lines with my questions. Now I would like my program to randomly choose 5 of a question from table and show it to user.
First question is, can I do my table in Excel and use it than in Visual Studio? If not, what software should I use to do table like this as simply as possible, and how to implement it into my Visual studio project? If yes, how to implement Excel table to my Visual studio project?
Second question is, what simplest code should I write to randomly choose 5 of 100 question from my table?
A code of a question is:
int main()
{
string ans; //Users input
string cans; //Correct answer, comes from table
string quest; //Question, comes from table
string a; // Answers, come from table
string b;
string c;
string d;
int points; //Amount of points
points = 0;
cout << "\n" << quest << "\n" << a << "\n" << b << "\n" << c << "\n" << d << "\n";
cout << "Your answer is: ";
cin >> ans;
if (ans == cans)
{
points = points + 1;
cout << "Yes, it is correct\n";
}
else
{
cout << "No, correct answer is " << cans << "\n";
}
return 0;
}
First Part
You can definitely use Excel to edit your questions and save them. But when you save it, pay attention to the file format.
You want to save your Excel file as a .csv file instead of .xls or .xlsx file. Just go to File -> Save File As -> and change type to .csv.
This is because, reading .csv files is a lot easier. .csv files have each cell in row separated by a comma (,) and each row by a newline ('\n') character.
So, here is a sample Excel file:
After I save it as a .csv file and open it using a text editor (Atom, here), it looks like this:
After, that you only need to write some code to read the file.
This is the code I used (I've added excessive comments to make the code more explicit for beginners):
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
using namespace std;
const int MAX_QUESTIONS = 3;
const int COLUMNS = 6; //Question, Options A, B, C, D, Correct Answer
int main() {
ifstream fp;
fp.open("test.csv");
//If file could not be opened
if (fp.fail()) {
std::cout << "failed to open file" << std::endl;
return 1;
}
//Create a 2D vector of strings to store the values in the file
vector< vector<string> > table;
string line;
//Loop through the entire file (fp)
//Store all values found until I hit a newline character ('\n') in the string line
//This loop automatically exits when the end-of-file is encountered
while (getline(fp, line, '\n')) {
//Create an temporary vector of strings to simulate a row in the excel file
vector<string> row;
//Now I am passing in this string into a string stream to further parse it
stringstream ss;
ss << line;
string part;
//Similar to the outer loop, I am storing each value until I hit a comma into the string part
while (getline(ss, part, ',')) {
//Add this to the row
row.push_back(part);
}
//Add this row to the table
table.push_back(row);
}
//Print out the entire table to make sure your values are right
for (int i = 0; i <= MAX_QUESTIONS; ++i) {
for (int j = 0; j < COLUMNS; ++j) {
cout << table[i][j] << " ";
}
cout << endl;
}
return 0;
}
Second Part
To choose a random number, you can use this code (I borrowed it from another answer)
#include <random>
std::random_device rd; // only used once to initialise (seed) engine
std::mt19937 rng(rd()); // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased
auto random_integer = uni(rng);
Unlike the old method, this doesn't create bias towards the lower end. However, the new engine is available only in C++11 compilers. So keep that in mind. If you need to use the old method, you can correct the bias by following this answer.
To choose 5 different numbers, each time you generate a random number store it in an array and check whether this number has already been used. This can prevent repetition of questions.
To answer question 1: In Excel, click File>Save As. Choose UTF-16 Unicode Text (.txt), name your file, and save it where your program can access it. In your C++ program, use the fstream library for text files:
#include <fstream>
With the text file in the same directory, use
ifstream fin;
fin.open("FILENAME.txt");
.
.
.
fin >> variable;
.
.
.
fin.close();
To answer question 2: There is a rand() function that returns an integer between zero and RAND_MAX. Then you can use modulus(%) to get a random number in the desired range.
#include <cstdlib> // for srand() and rand()
#include <ctime> // for time()
.
.
.
srand(time(NULL)); // seed the RNG on seconds
.
.
.
var = rand() % ONE_MORE_THAN_MAX;
Here's a quizzer I made to help myself study:
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>
#include <ctime>
using namespace std;
const char QUESTIONS_FILE_NAME[] = "questions.dat";
const char QUESTION_ANSWER_SEPARATOR = ',';
const char QUESTION_SEPARATOR = '\n';
const short MAX_QUESTION_LENGTH = 300;
const short MAX_ANSWER_LENGTH = 300;
const short MAX_NUM_QUESTIONS = 300;
int main()
{
char questions[MAX_NUM_QUESTIONS][MAX_QUESTION_LENGTH];
char answers[MAX_NUM_QUESTIONS][MAX_ANSWER_LENGTH];
short counter = 0;
short question_choice;
char user_answer[MAX_ANSWER_LENGTH];
ifstream fin;
fin.open( QUESTIONS_FILE_NAME );
srand(time(NULL));
while(fin.getline( questions[counter], MAX_QUESTION_LENGTH-1,
QUESTION_ANSWER_SEPARATOR ))
{
fin.getline( answers[counter], MAX_ANSWER_LENGTH-1,
QUESTION_SEPARATOR );
counter++;
}
fin.close();
cout << endl << "Press CTRL+C to quit at any time" << endl << endl;
while ( counter > 0 )
{
question_choice = rand() % counter;
cout << endl << questions[question_choice] << " ";
cin >> user_answer;
if ( strcmp( user_answer, answers[question_choice] ) )
{
cout << endl << "Incorrect" << endl
<< "It was " << answers[question_choice] << endl;
strcpy( questions[counter], questions[question_choice] );
strcpy( answers[counter], answers[question_choice] );
counter++;
}
else
{
cout << endl << "Correct" << endl;
counter--;
strcpy( questions[question_choice], questions[counter] );
strcpy( answers[question_choice], answers[counter] );
}
}
cout << endl << "You Win!!!!" << endl;
return 0;
}
questions.dat would have data like this
In what year was the Pendleton Civil Service Reform Act enacted?
a.1873
b.1883
c.1893
d.1903,b
In what year did Hurricane Katrina occur?
a.2001
b.2003
c.2005
d.2007,c
I am given an input string of n integers, separated by commas (e.g. "23,4,56"). I need to set a stringstream to represent this string and then use it to scan each integer into a vector. The elements of the vector (the integers in the list) will end up being output line by line. I am given the main(), and am simply responsible for writing parseInts(string str). For some reason, I keep getting a timeout. I'm guessing it's something in my while loops, specifically concerning how I am manipulating my sstream with str(), but I can't figure out exactly what is going on. I am new to sstream and C++ so any help would be appreciated!
#include <sstream>
#include <vector>
#include <iostream>
using namespace std;
vector<int> parseInts(string str) {
int a; //will use this to hold the value of the 1st int in the sstream
stringstream list_initial; //will iterate over this sstream
list_initial.str(str); //set sstream to represent input str
vector<int> list_final; //will return this final vector for output in main
while (!list_initial.str().empty()){ //stop iterating at end of string
list_initial>>a; //store leading int value in a
list_final.push_back(a); //add a to end of vector
while (!ispunct(list_initial.str()[0])){ //get to next int in list
list_initial.str(list_initial.str().erase(0,1));
};
list_initial.str(list_initial.str().erase(0,1)); //erase leading comma
};
return list_final;
};
int main() {
string str;
cin >> str;
vector<int> integers = parseInts(str);
for(int i = 0; i < integers.size(); i++) {
cout << integers[i] << "\n";
}
return 0;
}
Your function implementation could be really improved, but following your logic, if you replace the second while line:
while (!ispunct(list_initial.str()[0])){ //get to next int in list
by this one, adding a length check:
while (list_initial.str().size() && !ispunct(list_initial.str()[0])){ //get to next int in list
, then is runs, works well and exits well.
Explanations
This loop was never breaking, because at the end of the process, ispunct() function was never recognizing its parameter, !list_initial.str()[0], as something true : the string list_initial.str() is empty at this moment, then the 0 index doesn't exist.
Please look at the doc on cplusplus.com:
If pos is equal to the string length, the const-version never throws exceptions (no-throw guarantee).
Otherwise, it causes undefined behavior.
Your program was freezing because it didn't found the right condition which could have leaded to leaving the above loop.
Advices
One important thing to talk about your question is : why didn't you find the answer yourself ? One answer for this question is the following : you didn't try to debug your code.
To answer your question, I just debugged your code, and found quickly the problem just by adding this kind of lines around your code, to see what happened:
cout << "list_initial: " << list_initial.str() << endl;
An example for your code, with debug statements added::
#include <sstream>
#include <vector>
#include <iostream>
using namespace std;
vector<int> parseInts(string str) {
cout << "begin parseInts()" << endl; // ###### DEBUG ######
int a; //will use this to hold the value of the 1st int in the sstream
stringstream list_initial; //will iterate over this sstream
list_initial.str(str); //set sstream to represent input str
vector<int> list_final; //will return this final vector for output in main
cout << "begin while 1" << endl; // ###### DEBUG ######
while (!list_initial.str().empty()){ //stop iterating at end of string
list_initial >> a; //store leading int value in a
list_final.push_back(a); //add a to end of vector
cout << "a: " << a << endl; // ###### DEBUG ######
cout << "begin while 2" << endl; // ###### DEBUG ######
while (!ispunct(list_initial.str()[0])){ //get to next int in list
cout << "list_initial: " << list_initial.str() << endl; // ###### DEBUG ######
list_initial.str(list_initial.str().erase(0,1));
};
cout << "endwhile 2" << endl; // ###### DEBUG ######
list_initial.str(list_initial.str().erase(0,1)); //erase leading comma
};
cout << "endwhile 1" << endl; // ###### DEBUG ######
cout << "end parseInts()" << endl; // ###### DEBUG ######
return list_final;
};
int main() {
string str;
cin >> str;
vector<int> integers = parseInts(str);
cout << "begin for" << endl; // ###### DEBUG ######
for(int i = 0; i < integers.size(); i++) {
cout << integers[i] << "\n";
}
cout << "end for" << endl; // ###### DEBUG ######
return 0;
}
This (very basic) debugging technic permits you to quickly find out a bug in your code; just add some cout << ... << endl; lines inside the code to point out some important informations, i.e.: "Which loop is causing freezing?", or "What is the content of this variable at this moment?".
If you put cout << "Entering While loop 1" << endl; before, and cout << "Exiting While loop 1" << endl; after each suspected loop, you may learn many things about what happens during the execution of your program.
With this debugging lines added, you can easily find out that the second loop is looping forever; then you can narrow your bug investigation to this loop.
A stream is an abstract file. It is just a bunch of text that can be understood by things like, for example, cin >> n to convert numbers to integers.
Here's an idea of how to use a stream:
#include <cctype>
#include <ciso646>
#include <iostream>
#include <sstream>
#include <vector>
using namespace std;
vector<int> parse_ints( const string& s )
{
vector<int> ints; // resulting list of ints
istringstream ss(s); // stream of ints separated by (non-digits)
int n; // each int extracted from the stream
// while input an int
while (ss >> n)
{
// save the int
ints.push_back(n);
// skip to next digit
while (ss and !isdigit(ss.peek())) ss.get();
}
return ints;
}
int main()
{
vector<int> xs = parse_ints( "2, 3 ,5; 7 : 11.13\t17abc21" );
for (int x : xs)
cout << x << " ";
cout << "\n";
}
This produces:
2 3 5 7 11 13 17 21
Notice how we can simply skip characters we aren't interested in? In this particular case, we skip all non-digit characters.
Hope this helps.
This question already has answers here:
getline not asking for input? [duplicate]
(3 answers)
Closed 9 years ago.
cout << "Type in your third message below:\n";
getline(cin, msgth);
if (msgth.length() > 0 && msgth.length() < 500) {}
else
{
system("cls");
cout << "Your message has to be between 1 and 500 characters long...";
goto prot;
}
So, whenever I get to this piece of code, it's like it automatically presses return, and "skips" the getline() function (AKA, goes to the prot label). The same thing happens further up for some reason. However, after a bit of experimenting, I've found out that when using this:
input:
if (special == 0)
{
cout << "Choose your input message below:\n";
getline(cin, inp);
if (inp.length() > 0 && inp.length() < 500) {}
else
{
system("cls");
cout << "Your message needs to be between 1 and 500 characters long\n";
goto input;
}
}
It does work the second time (with other words, after going to the input label). The difference between these two codes is that the first one has to bypass a std::cin code before getting back to getline(), while the other one doesn't.
A solution and some explaination would be gladly appreciated.
The following works for me:
#include <iostream>
#include <string>
int main() {
std::string str;
start:
std::cout << "prompt:\n";
std::getline(std::cin, str);
if (0 < str.length() && str.length() < 20) {}
else {
std::cout << "invalid.\n";
goto start;
}
std::cout << "input: \"" << str << "\"\n";
}
How is yours different from this?