How to access other comma separated values from a txt file. C++ - c++

ifstream f("events.txt");
if (f.is_open())
{
string l,t;
string myArray[5];
int i = 0;
while (getline(f, l))
{
getline(stringstream(l), t, ',');
cout << t << endl;
myArray[i] = t;
cout << myArray[i] << endl;
i = i + 1;
}
So I have a file called 'events.txt' that contains:
An angry orc hits you with his weapon!,-50
A mage casts an evil spell on you!,-20
You found a health potion!,25
An enemy backstabs you from the shadows!,-40
You got eaten by a Dragon!,-1000
This part of the program so far prints out the sentence up to the comma and stores it into an array.
My problem is I also want to access the number in some way and store it into another array or to use it when the event occurs, as it'll be used to lower player HP.
Thanks in advance!

A simple way to do this:
Define a struct to store your data:
struct Event {
std::string message;
int number;
}
(Don't store both items in separate arrays, they belong together.)
Create a vector of this struct and add the items to it:
std::vector<Event> events;
while (getline(f, l)) {
size_t pos = l.find(',');
Event e;
e.message = l.substr(0, pos);
e.number = std::stoi(l.substr(pos+1));
events.push_back(e);
}
However this assumes that the string has exactly one comma. If you want to be more flexible, use std::strtok or regular expressions.
Another recommendation: Separate I/O from parsing. Don't try to read data types like int directly from the input stream, as suggested in one of the comments. Instead, read the whole line or whatever your parsing unit is and then do the parsing. This makes your code more readable and simplifies error handling and debugging.

Related

Binary search check if string contains a string

I have a problem with using binary_search, it works, but only if the whole string is inserted as the search-key
I want it to work without searching after whole string, but just a key word and return "found" if a string(search-key) is part of another string(from sorted vector of strings)
case 5: // case til søgning efter telefonnummer
cout << "Indtast telefonnummer til soegning: " << endl;
getline(cin >> ws, key);
vector<string> mylines_sorted;
for (int i = 0; i < mylines.size(); i++) {
mylines_sorted.push_back(mylines[i]); // vector of strings is transferred to new vector of strings
}
sort(mylines_sorted.begin(), mylines_sorted.end());
for (int i = 0; i < mylines.size(); i++) {
cout << mylines_sorted[i] << endl; // just a check if data is sorted
}
bool result = binary_search(mylines_sorted.begin(), mylines_sorted.end(), key);
cout << result << endl; // another check
if (result == false) {
cout << "Soegning gav intet...!" << endl;
}
else {
cout << "Soegning: " << key << " findes i datafil!" << endl;
}
break;
}
return 0;
string line;
vector<string> mylines;
while (getline(database, line)) {
mylines.push_back(line);
}
I don't know if this part is relevant, I dont think so, but I transfer data from data file to vector of strings
struct Data {
char navn[80];
char addresse[80];
int alder;
unsigned int tlf;
};
There's a very simple way to get "words" from a string: Put the string into an std::istringstream and use std::istream_iterator<std::string> to get words out of it.
Combine this with the vectors insert function to add the strings to the vector you sort and search.
For example something like this:
// For each line...
for (auto const& line : mylines)
{
// Put the line into an input string stream
std::istringstream iss(line);
// Read from the string stream, adding words to the sorted_mylines vector
sorted_mylines.insert(end(sorted_mylines),
std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>());
}
After the above, sorted_mylines will contain all the words from all the lines in mylines.
You can now sort it and search for individual words. Or just skip the sorting and do a linear search.
Considering your edit, and the structure you use, I suggest you first read the file, parse each line into the corresponding structure, and create a vector of that instead of working with lines.
Then you could easily search for either name (which I recommend you split into separate first and last name) or address (which I recommend you also split up into its distinct parts, like street name, house number, postal code, etc.).
If you split it up then it will become much easier to search for specific parts. If you want a more generic search then to a linear loop over all entries, and look in all relevant structure members.

Reading integers and strings from a text file and storing in parallel arrays

I have a text file that stores the index, student name and student ID and I am trying to read them into an array of integers index, arrays of strings studentName and studentID. I'm having problems storing the student's names because they could be more than a single word. I could separate the items in the text file by commas and use getline but that would mean the index array will have to be a string type. Is there a workaround for this without changing the original text file?
Original file:
1 James Smith E2831
2 Mohammad bin Rahman M3814
3 MJ J4790
const int SIZE = 3;
int index[SIZE];
string studentName[SIZE], studentID[SIZE];
fstream infile("students.txt");
if(infile.is_open()){
int i = 0;
while(i < 3){
infile >> index[i] >> studentName[i] >> studentID[i];
i++;
}
}
Changed file:
1,James Smith,E2831
2,Mohammad bin Rahman,M3814
3,MJ,J4790
const int SIZE = 3;
string index[SIZE];
string studentName[SIZE], studentID[SIZE];
fstream infile("students.txt");
if(infile.is_open()){
int i = 0;
while(i < 3){
getline(infile, index[i],','); //index array is string
getline(infile, studentName[i],',');
getline(infile, studentID[i],'\n');
i++;
}
}
It's an error to read one line into one student property with the given input format. You need to read one line and then split the information in this line into the 3 properties.
std::stoi can be used to convert to convert the first part of the line read to an int. Futhermore it's simpler to handle the data, if you create a custom type storing all 3 properties of a student instead of storing the information in 3 arrays.
Note that the following code requires the addition of logic to skip whitespace directly after (or perhaps even before) the ',' chars. Currently it simply includes the whitespace in the name/id. I'll leave that task to you.
struct Student
{
int m_index;
std::string m_name;
std::string m_id;
};
std::vector<Student> readStudents(std::istream& input)
{
std::vector<Student> result;
std::string line;
while (std::getline(input, line))
{
size_t endIndex = 0;
auto index = std::stoi(line, &endIndex);
if (line[endIndex] != ',')
{
throw std::runtime_error("invalid input formatting");
}
++endIndex;
auto endName = line.find(',', endIndex);
if (endName == std::string::npos)
{
throw std::runtime_error("invalid input formatting");
}
result.push_back(Student{ index, line.substr(endIndex, endName - endIndex), line.substr(endName + 1) });
}
return result;
}
int main() {
std::istringstream ss(
"1,James Smith,E2831\n"
"2, Mohammad bin Rahman, M3814\n"
"3, MJ, J4790\n");
auto students = readStudents(ss);
for (auto& student : students)
{
std::cout << "Index=" << student.m_index << "; Name=" << student.m_name << ";Id=" << student.m_id << '\n';
}
}
There are so many possible solutions that it is hard to tell, what should be used. Depends a little bit on your personal style and how good you know the language.
In nearly all solutions you would (for safety reasons) read a complete line with std::getline then put either split the line manually or use a std::istringstream for further extraction.
A csv input would be preferred, because it is more clear what belongs together.
So, what are the main possibilities? First the space separated names
You could use a std::regexand then search or match for example "(\d) ([\w ]+) (\w+)"
You cou create substrings, by searching the first space from the right side of the string, which would be the "studentID", then the getting the rest is simple
You could use a while loop and read all parts of the string and put it in a std::vector. The first element in the std::vector is then the index, the last the ID and the rest would be the name.
You could parse the string with formatted input functions. First read the index as number, then the rest as stringm the split of the last part and get the id.
And many more
For csv, you could use
the std::sregex_token_iterator looking for the comma as separator
Use also a std::vector and later pick the needed values.
Use a mixture of formatted and unformatted input
Example:
std::getline(std::getline(infile >> index >> Comma >> std::ws, name, ','), id);
It is up to you, what you would like to implement.
Write your preference in the comment, then I will add some code.

Read from a file to three parallel array

As part of my assignment I need to open a file and then read the information into 3 arrays. These information are separated into 3 different columns first one is, Country Code name(is string), second is the population(is int) and the third is the full name of the country. Here is the example of few lines of the file:
AU 20090437 Australia
BR 186112794 Brazil
BU 7262675 Bulgaria
CA 32805041 Canada
CN 1306313812 China
DO 8950034 Dominican Republic
So far I have:
void readCntrData(string [], int [], string[], int &size, const int maxSize);
int main()
{
const int COUNTRIES = 200;
// maximum size of arrays
int size = 0;
string cntrCodes[COUNTRIES];
int cntrPopulation[COUNTRIES];
string cntrNames[COUNTRIES];
string inputFileName = "countries.txt";
ifstream inputFile;
inputFile.open(inputFileName.c_str());
if (inputFile.fail())
{
cout << "\n\tPlease check the name of the input file and \n\ttry again later!\n";
exit(EXIT_FAILURE);
}
int index = 0;
while (index < COUNTRIES && inputFile >> cntrCodes[index] >> cntrPopulation[index] >> cntrNames[index] ) {
index++;
}
size = index;
if (size == COUNTRIES && !inputFile.eof()){
cout << "\n\tThe input file \"" << inputFileName <<
"\"is too big: \n\tit has more than " << COUNTRIES << " items!\n";
exit(EXIT_FAILURE);
}
inputFile.close();
}
The issue here few countries have two part names, and my code breaks where the name of country has two parts. I don't know how to ignore the space there and read the whole name.
I appreciate any feedback.
Just cleaning the code up a bit...
the main problem you ask about in your question has already been addressed by user4581301 in comments: summarily, using getline to read the country code will cope with having one or more whitespace-separated words
since C++11, we've been able to use the ifstream constructor to open a filename passed in a std::string, and it's often convenient to construct the ifstream inside an if/else construct that then handles the successful-open and failed-attempt-to-open cases
there's no need to explicitly close ifstreams that are going out of scope anyway
it's good to send error messages to std::cerr, so if someone running your program does something like "theprogram > theprogram.output" they'll still see the error messages in their terminal
no real need to have separate index and size variables, and something like num_countries is a better, more meaningful name
checking for EOF is a bit of a dark art - I wouldn't recommend using inputFile.eof() the way you did because if the last country read was followed by an empty line in the file, you wouldn't have hit end-of-file even though it's "logically" empty thereafter; it's too easy for someone not to notice empty lines at the end of a file when they're working in a editor to create/update the file; checking if there's another non-whitespace character is a more robust approach
the common size for int these days is 32 bits, which can handle numbers up to a couple billion (when signed, or a bit over 4 billion for unsigned int); China's population's a bit too close to that for comfort - if you want to write code in a way that should still work 20 or 50 years hence, it's good to think about whether the types will still be large enough to store the values; int64_t from <cstdint> is a better choice.
Revised code:
int main()
{
const int COUNTRIES = 200;
string cntrCodes[COUNTRIES];
int64_t cntrPopulation[COUNTRIES];
string cntrNames[COUNTRIES];
string inputFileName = "countries.txt";
if (std::ifstream inputFile{inputFileName})
{
int num_countries = 0;
while (num_countries < COUNTRIES &&
inputFile >> cntrCodes[num_countries] >> cntrPopulation[num_countries] &&
getline(std::cin, cntrNames[num_countries]))
++num_countries;
// will see if we can read another character (remember >> skips
// whitespace by default), rather than check inputFile.eof()
// which may not be true if there're any extra empty lines after
// the data...
char character;
if (num_countries == COUNTRIES && inputFile >> character)
{
std::cerr << "\n\tThe input file \"" << inputFileName <<
"\"is too big: \n\tit has more than " << COUNTRIES << " items!\n";
exit(EXIT_FAILURE);
}
// any extra code to actually use the country data goes here...
}
else
{
std::cerr << "\n\tPlease check the name of the input file and \n\ttry again later!\n";
exit(EXIT_FAILURE);
}
}
For the last input of each line, you can use getline which reads everything until it meets an Enter key.
You can try this:
while (index < COUNTRIES && inputFile >> cntrCodes[index] >> cntrPopulation[index] ) {
getline (inputFile, cntrNames[index]);
index++;
}

How to do a for loop over values read by a string stream in c++?

I have a program that is supposed to read an input file via string stream. The file contains student names, and test scores that I want to average. The file can contain any number of test scores for any number of students greater than 1 and less than 10.
If I am reading all the values in the file by string stream, how would I store each test score value as an integer where I can sum them? Here is the code I have so far, which I am not sure is even correct:
string fname, lname, line;
getline(cin, line);
istringstream sin;
sin.str(line);
sin >> fname >> lname;
Is this the right way to parse through values? At the top, I declared a struct 'student' like this:
struct student {
string first_name;
string last_name;
double avg_score;
} student1;
Thank you!
If each line varies in the number of scores I'd tend to read in complete lines and parse them, one after the other. Thereby you can rely on the >>-operator to return false once no more score could be read in a line. So I think you are on the right way. See the following code demonstrating how to deal with the return values of >>:
int main() {
ifstream f(DATAFILE);
if(f) {
string line;
while (getline(f,line)) {
string fname,lname;
istringstream ss(line);
if (ss >> fname >> lname) {
double sum = 0;
double value;
int count = 0;
while (ss >> value) {
sum += value;
count++;
}
cout << line << " gives average: " << sum/count << endl;
}
}
}
}
Storing the values in a struct is straight forward (and left up to you :-)). In case you face troubles please ask.
Hope it helps.
If you need to store multiple data you typically need a so-called container class. Container classes can store an arbitrary number of data of the same type and provide methods to manage the container elements.
The standard container class in C++ is std::vector, so, for example, in your case you may define a container by
#include <vector>
std::vector<student> allStudents;
To add the data of student1 you may do
allStudents.push_back(student1)
For anything else you better read a beginner's C++ text book, as working with container classes is a basic skill of a C++ programmer.

Output not displaying all information C++

I have to create a program that will output various information about 1343 runners in a marathon. I'm having to import the data from a csv spreadsheet, so I chose to use the getline function. I use simple recursion to fill a string array and then simply use recursion once more to output the data. But for some reason, it only wants to display 300 or so runners' data. Here's the code:
int main(){
string data[1344];
vector<string> datav;
string header;
ifstream infile("C:\\Users\\Anthony\\Desktop\\cmarathon.csv");
int i = 0;
if (infile.is_open()) {
for (i=0; i<=1343; i++) {
getline(infile, data[i]);
}
datav.assign(data, data+1344);
for (int i = 0; i < datav.size(); i++) {
cout << datav[i] << "\n";
}
}
}
I attempted to use a vector in hopes it would help to allocate the required memory to execute the program properly (if that is in fact the problem here).
That code yields the perfect output of runners 1045-1343. I've tried simple work arounds, such as using several for() loops to combine the output seamlessly to no avail. Any information would be appreciated.
You do not need to copy from the array to the vector. You can add to the vector directly instead. Also, it is somewhat bad practice to shadow another local variable at the outer scope.
int main(){
string line;
vector<string> datav;
string header;
ifstream infile("C:\\Users\\Anthony\\Desktop\\cmarathon.csv");
if (infile.is_open()) {
// Are you supposed to read the header line first?
getline( infile, header );
while( getline( infile, line ).good() )
datav.push_back( line );
cout << "Container has " << datav.size() << " lines\n";
for (size_t i = 0; i < datav.size(); i++) {
cout << datav[i] << "\n";
}
}
}
Of course, you still have to break down each line to the individual fields, so pushing back a class or struct as EToreo suggested would be a good idea.
You should try using a struct to represent the fields in the CSV file and then make a vector of that struct type.
Now, loop through the file, reading each line till you reach the end of the file (Google how to do that) - DO NOT assume 1343, you don't have to. When you read in each line, create a new object from your struct and fill it with the content of that line (you will need to parse it by reading till a tab (\t) or the end of the string) and then datav.push(newObj) it onto your vector.
I suggest using the correct type's in your struct (int for age, string for name, etc.) and passing the string values from the file into those types. It will be much easier to do things like make a sum of everyone's age. You will thank yourself (and maybe me?) later.
If your not needing to use a vector:
for (i=0; i<=1343; i++) {
cout << data[i] << endl;
}
should work to print out whatever is in the data array
It is also possible to specify a delimeter for the getline function if you need to put different strings in different variables.
However EToreo's method may be more useful to you in the long run.