I am trying to write a program in C++ that emulates a college enrollment system, where the student enters their ID, and the program searches a text file for their information, and loads a struct based on the text file. I have gotten to a point where I am having trouble getting their enrolled courses into the struct's array. Using the getline function, using the ',' as the delim will also carry over the next line until the next comma. What would be the correct algorithm for this?
This is the file setup that contains fake student information:
918273645,Steve,Albright,ITCS2530,MATH210,ENG140
123456789,Kim,Murphy,ITCS2530,MATH101
213456789,Dean,Bowers,ITCS2530,ENG140
219834765,Jerry,Clark,MGMT201,MATH210
(Bullets added for layout; not in the file)
For example, the user enters "123456789" for their ID, and Kim Murphy's information is then read. Upon the first iteration of getline, "ITCS2530" is read and put into the variable, and is then loaded into the struct; no problem there. However, the last course in the list has the newline character before the next comma, so the next iteration reads "MATH101/nl213456789" and puts the entire string into the variable and tries to load that into the struct.
The first column is their ID, then their First name, last name, and then their currently-enrolled courses after that. Notice that the number of enrolled courses can vary.
Here is the code that I am currently working on:
student login()
{
string ID;
student newStudent;
string enrolled;
int i = 0;
while (true)
{
cout << "Enter Student ID: ";
cin >> newStudent.ID;
cout << endl;
if (newStudent.ID.length() == 9)
break;
else
cout << "That ID is invalid - IDs are 9 digits" << endl;
}
ifstream inFile;
ofstream outFile;
inFile.open("registration.txt");
if (inFile.is_open())
//Check if file is open
{
while (!inFile.eof())
//While not at end of file
{
getline(inFile, ID, ',');
//Search for ID
if (ID == newStudent.ID)
{
getline(inFile, newStudent.fName, ',');
//Assign fName and lName
getline(inFile, newStudent.lName, ',');
while (enrolled != "\n")
{
getline(inFile, enrolled, ',');
if (enrolled == "\n")
{
cout << "Not currently enrolled in a class." << endl;
}
else
{
newStudent.courses[i] = enrolled;
i++;
}
}
cout << newStudent.lName << ", Welcome to the MCC Enrollment System!" << endl;
for (i = 0; i <= NUM_OF_COURSES; i++)
{
cout << "Enrolled courses: " << newStudent.courses[i] << endl;
}
cout << endl;
break;
//Stops searching
}
else
//Ignores rest of line - used to skip to the next line
{
getline(inFile, ID, '\n');
}
if (inFile.eof())
//If ID was not found
{
inFile.close();
cout << "Enter First Name: ";
//Begin entry of new student
cin >> newStudent.fName;
cout << endl;
cout << "Enter Last Name: ";
cin >> newStudent.lName;
cout << endl;
outFile.open("registration.txt", ios::app);
if (outFile.is_open())
{
outFile << newStudent.ID << "," << newStudent.fName << "," << newStudent.lName << "\n";
}
}
}
}
return newStudent;
}
Thanks in advance for any help.
The problem is that std::getline takes exactly one character as a delimiter. It defaults to a newline but if you use another character then newline is NOT a delimiter any more and so you end up with newlines in your text.
The answer is to read the entire line into a string using std::getline with the default (newline) delimiter and then use a string stream to hold that line of text so you can call std::getline with a comma as a delimiter.
Something like this:
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
int main()
{
std::ifstream inFile("registration.txt");
if (inFile.is_open())
{
std::string line;
while( std::getline(inFile,line) )
{
std::stringstream ss(line);
std::string ID, fname, lname;
std::getline(ss,ID,','); std::cout<<"\""<<ID<<"\"";
std::getline(ss,fname,','); std::cout<<", \""<<fname<<"\"";
std::getline(ss,lname,','); std::cout<<", \""<<lname<<"\"";
std::vector<std::string> enrolled;
std::string course;
while( std::getline(ss,course,',') )
{
enrolled.push_back(course); std::cout<<", \""<<course<<"\"";
}
std::cout<<"\n";
}
}
return 0;
}
In this example I am writing the text to the screen surrounded by quotes so you can see what is read.
split(string, seperator)
split("918273645,Steve,Albright,ITCS2530,MATH210,ENG140", ",")
split("123456789,Kim,Murphy,ITCS2530,MATH101", ",")
split("213456789,Dean,Bowers,ITCS2530,ENG140", ",")
split("219834765,Jerry,Clark,MGMT201,MATH210", ",")
I know very little C++, but I remember this command.
Related
C++ beginner here. I am storing structs of playerData in a vector (referred to as the player bank). One member of the struct is the player's name (stored in the struct as 1 string like "Julio Jones"). While using my application, if the user inputs a name that is not in my program's premade bank of players, the user is prompted to add the new name to the player bank for future use. The name input code is:
std::string input{};
std::cout << "Enter the player " << m_name << " traded: ";
std::getline(std::cin, input);
At the end of using the program, the user can save any changes they have made to the player bank for future use. I did so by writing the vector to a txt file
if (startAnswer == "exit")
{
std::cout << "Exiting Trade Analzyer...\n";
std::ofstream myfile;
myfile.open("playerbank.txt");
for (int k = 0; k < allPlayers.size(); ++k)
{
myfile << allPlayers[k].playerName << ' ' << allPlayers[k].playerPosition << ' ' << allPlayers[k].playersTeam << ' ' << allPlayers[k].playerValue << '\n';
}
myfile.close();
return 0;
}
There are no issues at this point. However, the issue arises the next time the user tries to run the program and use their updated player bank: any new player they had previously entered into the bank that only had a first name will not be extracted from the txt file because I extract the values as follows:
std::istream& operator>> (std::istream& input, playerData& data)
{
std::string first;
std::string last;;
input >> first >> last;
input >> data.playerPosition;
input >> data.playersTeam;
input >> data.playerValue;
data.playerName = first + ' ' + last;
return input;
}
std::vector<playerData> createPlayerBankFromFile()
{
std::vector<playerData> playerBank{};
playerData playerStruct;
std::ifstream inFile;
inFile.open("playerbank.txt");
while (inFile >> playerStruct)
{
playerBank.push_back(playerStruct);
}
inFile.close();
return playerBank;
}
The issue is of course that I am trying to extract two strings, but there is only 1 when it encounters a new player struct that the user added to the player bank with only a first name. It seems to me that an easy solution to this would be to write a loop to ensure that the user inputs both a first and last name when creating new players, but how can I do so? I could do two extraction lines like:
std::string first;
std::string last;
std::cout << "Enter player's first name: ";
std::getline(std::cin, first);
std::cout << "Enter the player's last name: ";
std::getline(std::cin, last);
then concatenate the strings to make the full player name, but I'd like to prompt the user once and get the full player name in one line.
You are assuming that people have only 1 first name and 1 last name separated by 1 space. Names are not always that simple.
You should prompt the user for 1 string, save that to the playerName, and then store the entire playerName as-is to your file followed by a delimiter that is guaranteed to never appear in any name, whether that be a line break, or any other character you want (other than space), eg:
myfile.open("playerbank.txt");
for (int k = 0; k < allPlayers.size(); ++k)
{
myfile << allPlayers[k].playerName << '|' << ... << '\n';
}
myfile.close();
Then, when reading back a name from the file, you can use std::getline() to read directly into playerName (get rid of first and last completely) until your chosen delimiter is reached, eg:
#include <limits>
std::istream& operator>> (std::istream& input, playerData& data)
{
// read until the delimiter is reached...
std::getline(input, data.playerName, '|');
// read the other values from input as needed...
// don't forget to read and discard the trailing line break at the end!
input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return input;
}
Alternatively:
#include <sstream>
std::istream& operator>> (std::istream& input, playerData& data)
{
std::string line;
if (!std::getline(input, line)) //<-- handles the trailing line break for you
return input;
std::istringstream iss(line);
// read until the delimiter is reached...
std::getline(iss, data.playerName, '|');
// read the other values from iss as needed...
return input;
}
Name~Proper Name~HR number~HD number~distance- format of the data in file
include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string name;
string properName;
string hrN;
string hdN;
double distance;
ifstream fin;
string fileName;
cout << "Enter a file name: ";
getline(cin, fileName);
fin.open(fileName);
if(fin.fail())
{
cout << "Unable to open the file" << endl;
return -1;
}
else
{
string name;
string properName;
string hrN;
string hdN;
double distance;
string line;
string pN1;
bool found = false;
while(true)
{
getline(fin, name, '~');
getline(fin, properName,'~');
getline(fin, hrN,'~');
getline(fin, hdN, '~');
fin >> distance;
cout << "Enter a proper star name:";
string pN1;
cin >> pN1;
if(pN1.compare(properName))
{
found = true;
cout << "Star : "<< "proper name: "<<pN1<< "distance: "<<distance<<"light years" << "HD num: "<<hdN << "HR num: "<< hrN << "common name: "<< name << endl;
}
}
if (found == false)
{
cout << "No star with the properName"<<" "<< pN1 <<" "<<"was found"<< endl;
}
fin.close();
}
return 0;
}
This is what Ive got so far.
I'm just not sure how to store the variables, in order to search for a variable and display the contents of a specific line to the screen
There's quite a lot wrong with your code. You've duplicated the variable names for some reason, your input loop never terminates and you've asked the question about the star name inside the input loop. I'd say you're rushing into coding without thinking about what you are doing or knowing where you are going.
Lets take it one piece at a time.
The first thing you have is a bunch of related data about stars, name, proper name, distance etc. So lets express that fact by grouping the related data in a struct
struct StarData
{
string name;
string properName;
string hrN;
string hdN;
double distance;
};
Now lets write a loop that reads that data and importantly finishes when there is no more data
StarData data;
// loop until no more data
while (getline(fin, data.name, '~') && getline(fin, data.properName, '~') &&
getline(fin, data.hrN, '~') && getline(fin, data.hdN, '~') &&
(fin >> data.distance))
{
}
So this loop reads star data, one star at a time into the data variable. But as yet doesn't do anything with that data.
There are various ways to store the data so that it will be searchable, it's a very common thing to want to do, so C++ does most of the work for you. I'm going to use something called a std::map which is a data structure which is very good at storing data so it can be searched. Let modify the loop above
#include <map>
std::map<string, StarData> star_database;
StarData data;
// loop until no more data
while (getline(fin, data.name, '~') && getline(fin, data.properName, '~') &&
getline(fin, data.hrN, '~') && getline(fin, data.hdN, '~') &&
(fin >> data.distance))
{
// add star data to database using proper name as the key
star_database[data.properName] = data;
}
This code stores the star data in a variable called star_database. In particular because we keyed the map on the proper name, i.e. because we said star_database[data.properName] = data; later we'll be able to use the proper name to look up the data about the star.
Now we've done the while loop, lets ask the question about the star to look up, remember this happens after the while loop above, not inside it.
cout << "Enter a proper star name:";
string pN1;
cin >> pN1;
Now we'll do the lookup
auto answer = star_database.find(pN1); // lookup the proper name in the database
if (answer == star_database.end()) // did we find it?
{
cout << "No star with the proper name "<< pN1 <<" was found"<< endl;
}
else
{
cout << "Star : "<< "proper name: "<< pN1 <<
" distance: "<< answer->second.distance << " light years" <<
" HD num: "<< answer->second.hdN <<
" HR num: "<< answer->second.hrN <<
" common name: "<< answer->second.name << endl;
}
I've skipped over quite a lot of detail (I can only explain so much) but hopefully you've got the basic idea and can research the rest yourself.
All code untested, apologies in advance for any typos.
I'm working on a project for university which requires me to find a value in a text file and replace it with a new value that I have stored in a float variable, part of the problem is that I do not know the item that needs to be replaced, only where it is (e.g. I could search for a value of 0.00 but multiple records could have that balance).
The text file is laid out like so:
Username1
Password1
AccountNo1
Balance1
Jimmy54
FooBar123
098335
13.37
...
And the relevant part of my code looks like this:
User enters their username and we store it in enteredName
#include "stdafx.h"
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Please enter your username:\n";
cin >> enteredName;
About 100 lines down in the code we get to the part important to this question:
if (c2 == 1)
{
//irrelevant to problem
}
else if (c2 == 2)
{
float convBal;
float deposit;
string newBal;
convBal = ::atof(ball.c_str());
/* Ball is declared higher up in the code and just contains a
value pulled from the text file for the sake of the question
we'll say it has a value of 0.00 */
cout << "How much would you like to deposit?:\n";
cin >> deposit;
newBal= convBal + deposit;
cout << "Your total balance is now: "<< newBal << "\n\n";
}
}
So what needs to be done from here is open the file, search the file for enteredName (in this case we'll say Jimmy54) and then replace the value 3 lines below the username (13.37) with the value stored in newBal
Thanks in advance! Sorry if this sounds like a stupid question, I'm quite new to programming and even newer to c++
EDIT #1:
Here is the code I have so far, it finds enteredName and then gets the 3 lines below:
if (myfile.is_open())
{
while (getline(myfile, line))
{
if (line.compare(enteredName) == 0)
{
getline(myfile, line); //Password
getline(myfile, line); //Account Number
getline(myfile, line); //Balance
}
}
}
myfile.close();
Here is the code that solved my problem, it's a bit sloppy but it works!
It simply reads into a new file and makes any changes and then writes back again.
if (myfile.is_open())
{
while (getline(myfile, line))
{
if (line.compare(enteredName) == 0)
{
tempFile << enteredName << "\n"; //Username
getline(myfile, line);
tempFile << line << "\n"; //Password
getline(myfile, line);
tempFile << line << "\n"; //Account Number
getline(myfile, line);
tempFile << newBal << "\n"; //Balance
}
else
{
tempFile << line << "\n"; //Username
getline(myfile, line);
tempFile << line << "\n"; //Password
getline(myfile, line);
tempFile << line << "\n"; //Account Number
getline(myfile, line);
tempFile << line << "\n"; //Balance
}
}
}
myfile.close();
tempFile.close();
/* Had to declare these again beacuse the compiler was throwing some bizarre error when using the old declarations */
ifstream tempF("TempStore.csv");
ofstream mainF("DataStore.csv", ios::trunc);
//Writes back from TempStore to DataStore with changes made correctly
if (tempF.is_open())
{
while (getline(tempF, line))
{
mainF << line << "\n"; //Username
getline(tempF, line);
mainF << line << "\n"; //Password
getline(tempF, line);
mainF << line << "\n"; //Account Number
getline(tempF, line);
mainF << line << "\n"; //Balance
}
}
tempF.close();
mainF.close();
I am trying to read the contents of a text file into a 2D string array, but no matter what I have tried or searched, I can not find a solution. The code is supposed to load a text file separate it into elements by finding horizontal tabs and display the output. When I run the code as is, I receive an error that I found out from searching online, means that I'm trying to manipulate memory I shouldn't be. I'm not asking for the code to be written, just a push in the right direction. Thank you in advance.
this is what I get when I run the program:
0x23fd5c
Process returned -1073741819 (0xC0000005) execution time : 2.344 s
Press any key to continue.
EDIT:: I have corrected the code so it now functions as it should, but it is not storing the last entry of each line in the text file correctly. I can somehow display the number 100 which is the last entry, but when I try to pass that location or display just playList[0][5] , it says it is equal to the first entry of the next line. Any help would be amazing I posted the current code below.
here is my code:
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdlib>
using namespace std;
void readTextFile( int &Count, string playList[50][5]);
void userAddition( int &Count, string playList[50][5]);
int main()
{
string decision;
string playList[50][5];
int Count = 0;
readTextFile(Count, playList);
cout << "If you would like to add to the list, Please enter 'Y'. If you would like to exit
please enter 'N'. ----> ";
getline(cin, decision);
if (decision=="y" || decision=="Y")
userAddition(Count, playList);
else
{
return(0);
}
return 0;
} // End of Main FN.
void readTextFile( int &Count, string playList[50][5])
{
string inputfield;
ifstream infile("c:\\cTunes.txt", ifstream::in);
if ( infile.is_open() )
{
// File is read.
} // end if
else
{
cout << "Error Opening file" << endl;
return; //Program Closes.
} // end else
cout << setw(30)<<left<< "TITLE"<< setw(10) <<left<<"LENGTH"<<
// Outputs a title to each column that is displayed.
setw(40)<< left<<"ARTIST"<< setw(40) << left<<"ALBUM"<<
setw(15) << left <<"GENRE" << setw(5) << left << "RATING" << endl;
getline(infile, inputfield, '\t'); // read until tab
while(! infile.eof()) // loop until file is no longer valid.
{
playList[Count][0] = inputfield;
getline(infile, inputfield, '\t'); // read until tab.
playList[Count][1] = inputfield;
getline(infile, inputfield, '\t'); // read until tab.
playList[Count][2] = inputfield;
getline(infile, inputfield, '\t'); // read until tab.
playList[Count][3] = inputfield;
getline(infile, inputfield, '\t'); // read until tab.
playList[Count][4] = inputfield;
getline(infile, inputfield); // read until end of line.
playList[Count][5] = inputfield;
cout << setw(30)<<left<< playList[Count][0] << setw(10) <<left<<playList[Count][1] <<
// Output the line number equal to count.
setw(40)<< left<<playList[Count][2] << setw(40) << left<< playList[Count][3] <<
setw(15) << left << playList[Count][4] << setw(5) << left << playList[Count][5] <<
endl;
/*cout <<"Title: " << setw(25)<<left<< playList[Count][0]<<endl;
cout <<"Length: " << setw(5) <<left<<playList[Count][1] << endl;
cout <<"Artist: " << setw(50)<< left<<playList[Count][2] << endl;
cout <<"Album: " << setw(40) << left<< playList[Count][3] << endl;
cout <<"Genre: " << setw(15) << left << playList[Count][4] << endl;
cout <<"Rating: " << setw(5) << left << playList[Count][5] << endl<<endl;*/
Count++; // Increment counter by 1
getline(infile, inputfield, '\t'); // read next line until tab.
} // end while
infile.close(); // close the file being read from.
cout<<endl<<endl<<playList[0][5]<<endl;
} // End of readTextFile
I believe getline is causing the problem when reading till the end of the line but I'm truly at a loss.
First, note that in the following line:
cout << playList << endl;
you are printing the address of the array, not the contents. you will need
a loop to print the contents.
Second, in the following code:
if ( infile.is_open() )
{
// ok, read in the file
} // end if
else
{
cout << "Error Opening file" << endl;
//a return statement is missing
}
you need a return statement to avoid reading from a closed stream (which can cause a crash)
Finally, your function does not return anything, so it should either be declared void
or return some value (in which there is no sense according to the context).
Hope that helps and good luck!
The readTextFile function does not return anything. You need to return a string. If you are getting the error what(): basic_string::_S_construct null not valid then this is your problem. You can avoid similar problems by using -Wall compiler option.
You are using std::string for storing the string, std::ifstream for reading from a file, std::getline for reading words... you should really use std::vectors instead of arrays:
typedef std::vector<std::string> Line;
std::vector<Line> playList;
It will make things easier for you.
I also recommend you to change the way you read from a file to:
while(getline(...))
{
...
}
But since you are not allowed to use std::vector, your function could look like this:
// reads the content of input file and stores it into playList:
void readTextFile(std::ifstream& infile, std::string playList[][5])
{
std::string line;
for (int lineIndex = 0; getline(infile, line); ++lineIndex)
{
// skip empty lines:
if (line.empty()) continue;
std::string word;
std::istringstream lineStream(line);
for (int wordIndex = 0; getline(lineStream, word, '\t'); ++wordIndex)
{
// skip empty words:
if (line.empty()) continue;
playList[lineIndex][wordIndex] = word;
}
}
}
that could be used like this:
std::string playList[19][5];
std::ifstream infile("c:\\Test.txt");
if (infile.is_open())
{
readTextFile(infile, playList);
infile.close();
}
else
std::cout << "Error Opening file" << std::endl;
Also note that cout << playList << endl; outputs the address of the first word. To print all words, you will have to write a loop.
I have the following structure:
struct productInfo
{
int item;
string details;
double cost;
};
I have a file that will input 10 different products that each contain an item, details, and cost. I have tried to input it using inFile.getline but it just doesn't work. Can anyone give me an example of how to do this? I would appreciate it.
Edit
The file contains 10 lines that look like this:
570314,SanDisk Sansa Clip 8 GB MP3 Player Black,55.99
Can you provide an example please.
Edit
Sorry guys, I am new to C++ and I don't really understand the suggestions. This is what I have tried.
void readFile(ifstream & inFile, productInfo products[])
{
inFile.ignore(LINE_LEN,'\n'); // The first line is not needed
for (int index = 0; index < 10; index++)
{
inFile.getline(products[index].item,SIZE,DELIMETER);
inFile.getline(products[index].details,SIZE,DELIMETER);
inFile.getline(products[index].cost,SIZE,DELIMETER);
}
}
This is another approach that uses fstream to read the file and getline() to read each line on the file. The parsing of the line itself was left out on purpose since other posts have already done that.
After each line is read and parsed into a productInfo, the application stores it on a vector, so all products could be accessed in memory.
#include <iostream>
#include <fstream>
#include <vector>
#include <iterator>
#include <string>
using namespace std;
struct productInfo
{
int item;
string details;
double cost;
};
int main()
{
vector<productInfo> product_list;
ifstream InFile("list.txt");
if (!InFile)
{
cerr << "CouldnĀ“t open input file" << endl;
return -1;
}
string line;
while (getline(InFile, line))
{ // from here on, check the post: How to parse complex string with C++ ?
// https://stackoverflow.com/questions/2073054/how-to-parse-complex-string-with-c
// to know how to break the string using comma ',' as a token
cout << line << endl;
// productInfo new_product;
// new_product.item =
// new_product.details =
// new_product.cost =
// product_list.push_back(new_product);
}
// Loop the list printing each item
// for (int i = 0; i < product_list.size(); i++)
// cout << "Item #" << i << " number:" << product_list[i].item <<
// " details:" << product_list[i].details <<
// " cost:" << product_list[i].cost << endl;
}
EDIT: I decided to take a shot at parsing the line and wrote the code below. Some C++ folks might not like the strtok() method of handling things but there it is.
string line;
while (getline(InFile, line))
{
if (line.empty())
break;
//cout << "***** Parsing: " << line << " *****" << endl;
productInfo new_product;
// My favorite parsing method: strtok()
char *tmp = strtok(const_cast<char*>(line.c_str()), ",");
stringstream ss_item(tmp);
ss_item >> new_product.item;
//cout << "item: " << tmp << endl;
//cout << "item: " << new_product.item << endl;
tmp = strtok(NULL, ",");
new_product.details += tmp;
//cout << "details: " << tmp << endl;
//cout << "details: " << new_product.details << endl;
tmp = strtok(NULL, " ");
stringstream ss_cost(tmp);
ss_cost >> new_product.cost;
//cout << "cost: " << tmp << endl;
//cout << "cost: " << new_product.cost << endl;
product_list.push_back(new_product);
}
It depends on what's in the file? If it's text, you can use the redirect operator on a file input stream:
int i;
infile >> i;
If it's binary, you can just read it in to &your_struct.
You have to
0) Create a new instance of productInfo, pinfo;
1) read text (using getline) to the first comma (','), convert this string to an int, and put it into pinfo.item.
2) read text to the next comma and put it into pinfo.details;
3) read text to the endline, convert the string to a double, and put it into pinfo.cost.
Then just keep doing this until you reach the end of the file.
Here is how I would use getline. Note that I use it once to read from the input file, and then again to chop that line at ",".
ostream& operator>>(istream& is, productInfo& pi)
{
string line;
getline(is, line); // fetch one line of input
stringstream sline(line);
string item;
getline(sline, item, ',');
stringstream(item) >> pi.item; // convert string to int
getline(sline, item, ',');
pi.details = item; // string: no conversion necessary
getline(sline, item);
stringstream(item) >> pi.cost; // convert string to double
return is;
}
// usage:
// productInfo pi; ifstream inFile ("inputfile.txt"); inFile >> pi;
N.b.: This program is buggy if the input is
99999,"The Best Knife, Ever!",16.95