reading file and split the line in c++ - c++

I have the following code that read input from txt file as follow
Paris,Juli,5,3,6
Paris,John,24,2
Canberra,John,4,3
London,Mary,29,4,1,2
my code is to load the data into map then I want to print the map content to make sure that it has been inserted correctly, I check the vaue of m as it is used during splitting the line. However, during the execution I get this as continues 0s which means it is never enter the while loop. I have used this part of code before and it works. I could not find where I've made the mistake.
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <vector>
#include<map>
using namespace std;
struct info {
string Name;
int places;// i will use the binary value to identfy the visited places example 29 is 100101
// this means he visited three places (London,LA,Rome)
vector<int> times; // will represent the visiting time,e.g. 1,2,5 means london 1 time, LA
// twice and Rome five times
};
map<string,vector<info> > log;
map<string,vector<info> >::iterator i;
fstream out;
int main() {
out.open("log.txt", std::ios::in | std::ios::out | std::ios::app);
string line;
char* pt;
string temp[19];
// for each line in the file
while (!out.eof())
{
getline(out,line);//read line from the file
pt=strtok(&line[0],"," );//split the line
int m=0;
while (pt != NULL)
{
temp[m++] = pt; // save the line info to the array
cout<<m<<" ";
pt = strtok (NULL, ",");
}
cout<<m<<" "; // during the execution I get this as continues 0s which means it is never enter the while loop
info tmp;
// read the other data
tmp.Name=temp[1];
tmp.places=atoi(temp[2].c_str());
for ( int i=3;i<=m;i++)
{
tmp.times.push_back(atoi(temp[i].c_str()));
}
// create a new object
log[temp[0]].push_back(tmp);
}
vector<int>::iterator j;
for(i=log.begin();i!=log.end();i++) {
cout<< "From "<< i->first<<" city people who travels: "<<endl;
for (size_t tt = 0; tt < (i->second).size(); tt++) {
cout<< (i->second[tt]).Name<< " went to distnations "<< (i->second)[tt].places<<" \nwith the folloing number of visiting time ";
for (j=((i->second[tt]).times).begin();j!= ((i->second[tt]).times).end();j++)
cout<<*j<<" ";
}
}
system("PAUSE");
return 0;
}

This is an error
// for each line in the file
while (!out.eof())
{
getline(out,line);//read line from the file
should be
// for each line in the file
while (getline(out,line))
{
I find it frankly incredible how often this error is repeated. eof does not do what you think it does. It tests if the last read failed because of end of file. You are using it to try and predict whether the next read will fail. It simply doesn't work like that.
This line is an error
pt=strtok(&line[0],"," );//split the line
strtok works on C strings, there's no guarantee it will work on std::string.
But neither of these are likely to be your real error. I would suggest opening the file with ios::in only. After all you only want to read from it.

Your fstream should not open in app mode. That will seek the file to the end of file. Delete std::ios::app from it.

You can't tokenize an std::string using strtok. Use getline instead:
std::string str("some,comma,separated,data");
std::string token;
while (getline(str, token, ',')) {
cout << "Token: " << token << end;
}
At each iteration, token contains the next parsed token from str.

This is wrong temp[m++] = pt; // save the line info to the array
Switch to something like this, instead of "temp"
std::vector<std::string> vTemp;
pt=strtok(&line[0],"," );//split the line
while (pt != NULL)
{
vTemp.push_back(pt); // save the line info to the array
pt = strtok (NULL, ",");
}
Also consider using something like this to do the split.
std::vector<std::string> SplitString(const std::string &strInput, char cDelimiter)
{
std::vector<std::string> vRetValue;
std::stringstream ss(strInput);
string strItem;
while(std::getline(ss, strItem, cDelimiter))
{
// Skip Empty
if(strItem.size()==0)
continue;
vRetValue.push_back(strItem);
}
return vRetValue;
}

#halfelf really great solution for my simple error, it works but the problem is now when I print the data I got this
From Paris city people who travels:
Juli went to distnations 5
with the folloing number of visiting time 3 6 0
John went to distnations 24
with the folloing number of visiting time 2 6
From Canberra city people who travels:
Johnwent to distnations 4
with the folloing number of visiting time 3 6
From London city people who travels:
Mary went to distnations 29
with the folloing number of visiting time 4 1 2 0
This is not correct as 6 is added to John from Canberra and Paris and 0 is added to Juli and Mary.
any idea of where I get it wrong ,, its about the times vector , its seems that I need to reset the value for each line or clear the content after the insertion. what about the extra 0?

Related

Logic for reading rows and columns from a text file (textparser) C++

I'm really stuck with this problem I'm having for reading rows and columns from a text file. We're using text files that our prof gave us. I have the functionality running so when the user in puts "numrows (file)" the number of rows in that file prints out.
However, every time I enter the text files, it's giving me 19 for both. The first text file only has 4 rows and the other one has 7. I know my logic is wrong, but I have no idea how to fix it.
Here's what I have for the numrows function:
int numrows(string line) {
ifstream ifs;
int i;
int row = 0;
int array [10] = {0};
while (ifs.good()) {
while (getline(ifs, line)) {
istringstream stream(line);
row = 0;
while(stream >>i) {
array[row] = i;
row++;
}
}
}
}
and here's the numcols:
int numcols(string line) {
int col = 0;
int i;
int arrayA[10] = {0};
ifstream ifs;
while (ifs.good()) {
istringstream streamA(line);
col = 0;
while (streamA >>i){
arrayA[col] = i;
col++;
}
}
}
edit: #chris yes, I wasn't sure what value to return as well. Here's my main:
int main() {
string fname, line;
ifstream ifs;
cout << "---- Enter a file name : ";
while (getline(cin, fname)) { // Ctrl-Z/D to quit!
// tries to open the file whose name is in string fname
ifs.open(fname.c_str());
if(fname.substr(0,8)=="numrows ") {
line.clear();
for (int i = 8; i<fname.length(); i++) {
line = line+fname[i];
}
cout << numrows (line) << endl;
ifs.close();
}
}
return 0;
}
This problem can be more easily solved by opening the text file as an ifstream, and then using std::get to process your input.
You can try for comparison against '\n' as the end of line character, and implement a pair of counters, one for columns on a line, the other for lines.
If you have variable length columns, you might want to store the values of (numColumns in a line) in a std::vector<int>, using myVector.push_back(numColumns) or similar.
Both links are to the cplusplus.com/reference section, which can provide a large amount of information about C++ and the STL.
Edited-in overview of possible workflow
You want one program, which will take a filename, and an 'operation', in this case "numrows" or "numcols". As such, your first steps are to find out the filename, and operation.
Your current implementation of this (in your question, after editing) won't work. Using cin should however be fine. Place this earlier in your main(), before opening a file.
Use substr like you have, or alternatively, search for a space character. Assume that the input after this is your filename, and the input in the first section is your operation. Store these values.
After this, try to open your file. If the file opens successfully, continue. If it won't open, then complain to the user for a bad input, and go back to the beginning, and ask again.
Once you have your file successfully open, check which type of calculation you want to run. Counting a number of rows is fairly easy - you can go through the file one character at a time, and count the number that are equal to '\n', the line-end character. Some files might use carriage-returns, line-feeds, etc - these have different characters, but are both a) unlikely to be what you have and b) easily looked up!
A number of columns is more complicated, because your rows might not all have the same number of columns. If your input is 1 25 21 abs 3k, do you want the value to be 5? If so, you can count the number of space characters on the line and add one. If instead, you want a value of 14 (each character and each space), then just count the characters based on the number of times you call get() before reaching a '\n' character. The use of a vector as explained below to store these values might be of interest.
Having calculated these two values (or value and set of values), you can output based on the value of your 'operation' variable. For example,
if (storedOperationName == "numcols") {
cout<< "The number of values in each column is " << numColsVal << endl;
}
If you have a vector of column values, you could output all of them, using
for (int pos = 0; pos < numColsVal.size(); pos++) {
cout<< numColsVal[pos] << " ";
}
Following all of this, you can return a value from your main() of 0, or you can just end the program (C++ now considers no return value from main to a be a return of 0), or you can ask for another filename, and repeat until some other method is used to end the program.
Further details
std::get() with no arguments will return the next character of an ifstream, using the example code format
std::ifstream myFileStream;
myFileStream.open("myFileName.txt");
nextCharacter = myFileStream.get(); // You should, before this, implement a loop.
// A possible loop condition might include something like `while myFileStream.good()`
// See the linked page on std::get()
if (nextCharacter == '\n')
{ // You have a line break here }
You could use this type of structure, along with a pair of counters as described earlier, to count the number of characters on a line, and the number of lines before the EOF (end of file).
If you want to store the number of characters on a line, for each line, you could use
std::vector<int> charPerLine;
int numberOfCharactersOnThisLine = 0;
while (...)
{
numberOfCharactersOnThisLine = 0
// Other parts of the loop here, including a numberOfCharactersOnThisLine++; statement
if (endOfLineCondition)
{
charPerLine.push_back(numberOfCharactersOnThisLine); // This stores the value in the vector
}
}
You should #include <vector> and either specific std:: before, or use a using namespace std; statement near the top. People will advise against using namespaces like this, but it can be convenient (which is also a good reason to avoid it, sort of!)

Is there a way to read in data after a specified word is encountered in a file?

The program I am creating will read in data from a text file, which contains a whole bunch of addresses and zip codes.
My question is: every time the file reads in "zip:" (if(text == "zip:"), the program should print out the tokens that come after it (the specifications ask for token oriented input), meaning the zip code numbers.
Is there a function of some kind that will only print out the zip code and none of the other text that comes after it? Sorry for the long post just want to give as much detail to the program as possible. If there is any other information I should include please let me know. I'm not looking for someone to give me complete program, just some guidance on that specific problem would be much appreciated.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
string text;
ifstream inFile;
inFile.open("zips");
while(!inFile.fail()) {
inFile >> text;
if(text == "zip:") {
}
}
inFile.close();
return 0;
}
The input is the file itself being looped through, the user does not enter any input.
My desired output is the top ten most frequent zip codes.
ex:
Zipz: Frequency:
11204 39
11234 33
22098 27....etc.
Here is a sample of what some of the file contains.
<8975.37428190#62997216886.XmT.srvr#n325.xnot.com> cc: visa addr: 488 Cicada Avenue =4=Z city: Edmonton zip: T5T4M4 $20.00 <833.337428190#2997439800.XmT.srvr#n324.xnot.com> cc: visa addr: 48030 Nevada Blvd =4=Z city: Montecito zip: 95041 $15.00 <8354.37428190#63001226169.XmT.srvr#n326.xnot.com> cc: visa addr: 493 Park Meadow Drive =4=Z city: Alamo zip: 94521 $10.00
<8857.37428190#63001517062.XmT.srvr#n326.xnot.com> cc: mastercard addr: 893 Moraga Avenue =4=Z city: San Bruno zip: 94012 $15.00
Assuming your input file will always be formatted like you posted, which means there will always have a value for zip(I didn't check for corner cases), that should do it:
#include <iostream>
#include <string>
#include <fstream>
#include <map>
using namespace std;
int main()
{
ifstream inFile;
inFile.open("test");
string text;
map<string, int> frequencies;
while (!inFile.fail())
{
inFile >> text;
if (text == "zip:" && !inFile.fail())
{
string zip;
inFile >> zip;
if (frequencies.find(zip) == frequencies.end())
frequencies[zip] = 1;
else
frequencies[zip]++;
}
}
map<string, int>::iterator it = frequencies.begin();
while (it != frequencies.end())
{
cout << (*it).first << ": " << (*it).second << endl;
++it;
}
return 0;
}
Ran it on your example file with 1 duplicate and got this output:
94012: 1
94521: 1
95041: 2
T5T4M4: 1
Formatting and sorting is missing though. Sorting can be implemented by putting the values from the map in a container supporting sort like set or vector for example.
Have a look at these answers to see how it can be done:
Vector: https://stackoverflow.com/a/8640935/109960
Set: https://stackoverflow.com/a/2699101/109960
Well, based on the file you have provided above I would go about solving this problem in this manner. I'm not going to provide field-tested, actual C++ code, but a general procedure.
First I would create a data structure to aggregate all the information we can get.
// Store all the zip codes
std::vector<int> codes;
Then I would begin to read the file character by character.
std::ifstream is(str); // open file
while (is.good()) // loop while extraction from file is possible
{
char c = is.get(); // get character from file
if (is.good())
{
if(c == 'z')
if(is.get() == 'i')
if(is.get() == 'p')
if(is.get() == ':')
{
// Extract the next 6 characters from the stream
// and store them as a string or something
// which you can later convert into an integer
// and push into the data structure we created earlier
}
}
}
is.close(); // close file
You can later count up the occurrences of each zip code in the vector, and then store up information about such in a std::map.

Input from text file, not displaying correctly, why?

Here is some part of my main:
int main() {
Inventory Master;
bool flag;
Customer Bob("Bob", "CreditCard.txt");
Customer Chris("Chris", "CreditCard.txt" );
}
Here is my method:
Customer::Customer( string n, string fileName ) {
name = n;
ifstream Credit;
Credit.open(fileName.c_str(), ios::in);
while( Credit.good() && !Credit.eof() ) {
Credit >> card >> balance >> ws;
cout << card <<"\t" << balance << endl;
}
CreditCard _CC( int card, double balance);
}
Here is my "CreditCard.txt file:
12345 15.00
32564 20.00
The way I wanted the info to display is have line 1 "12345 15.00" assigned to Bob and line 2 assigned to Chris and do that so on and so forth if i make new instances or objects of a customer. However the way I currently implemented it is it keeps assigning "12345 15.00 and 32564 20.00" to both Bob and Chris. I could appreciate the help if someone could SHOW me how to somehow point to certain lines of the text file so Bob is assigned to line 1, Chris to line 2, and more customers to other lines when i add them in the text file.
Everything you're doing to Bob and Chris happens inside the constructor. So, as written, your code says: while the stream is in good condition and it's not the end of the file(key point), write to here.
Well, if you think about it, this will read until the end of the file is reached for each instance of Customer. That's not what you want. I might suggest adding the name as the first field in the data file for each record. You could then search the file for the correct record, assuming you ensure the names are all uniquely defined, then pull the data out string by string. That way it's not reading from the beginning to the end each time. I added "Bob" as the first field on line 1, and "Chris" to line 2 and made string name = "Chris";. So...
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
string tempStr;
string name = "Chris";
ifstream Credit;
Credit.open("Info.txt", ios::in);
while( Credit.good() && !Credit.eof() )
{
getline(Credit, tempStr, ' ');//Reads the first records name field
cout << tempStr << endl;
if(name.compare(tempStr) == 0)//Compares the "name" to the field.
{ //If true they are the same
//Proceed to do reading and assignments with additional getline statements
cout << "Chris was matched the second time around!";
Credit.setstate(ios::eofbit, true);//***Sets eof to true
}
else
{
Credit.ignore(50, '\n');
//That should put Credit in the proper position to read the next name
}
}
}
The way you're doing it will cause problems. The only way that it would work for sure is if you knew where the record was at in the file. What if you had five records? By the time you got to the third one you would have to ignore, or similar, all the fields prior to the one you're working on. Also, it could be handy for a human to read a print out of the data file. Another reason to provide a label(name) to each record. Also, you're apparently using namespace std;, so I did too, but it's frowned upon.
istream.getline() http://www.cplusplus.com/reference/iostream/istream/getline/ could be your answer. Just read one line at a time.
A little example here:
http://www.cplusplus.com/forum/beginner/27799/
Little Example from one of my old homerworks:
ifstream fin(fileName);
char buffer[256];
int count = 0;
if (fin.is_open())
{
while (!fin.eof())
{
fin.getline(buffer, 256);
}
}

Tokenizing a string, errors with recognizing char

I'm having a bit of trouble with a school assignment for C++. The specific problem I'm having involves reading lines from a file that contain a series of between 5 and 6 grades. The grades for each student appear together in a single line following the student's name and id number. The challenge here is that the grades can have a variable number of spaces between them, and that if there are only 5 grades present, an error message needs to be generated to screen but the program is to continue running and average the 5 grades. Example of input: 23 46 68 85 98
I got the student name and id easily, but the string of digits is giving me problems. My plan was to getline and then tokenize the string and assign each token to a cell in an array. This works fine for 6 grades, but when only 5 grades are present it is assigning garbage to the sixth cell.
Here is the snippet of code that concerns this section:
fin.getline(gradeList, 200);
grade = strtok (gradeList, " ");
while (grade != '\0')
{
gradeArr[cycler] = atoi(grade);
grade = strtok(NULL, " ");
cycler++;
}
I tried doing an isdigit check on each token before converting it to an int, and writing a 0 in for any token that failed the isdigit check, but that didn't work at all. It seems like it is pulling the name from the next line when only 5 grades are present and then when it atoi's it, it changes it to a huge number.
I thought that when the program did getline, it would only grab the line until it saw the endline terminator. Is this not what is happening?
Scrap the C nonsense and use real C++:
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
// ...
std::vector<int> grades;
std::string line;
while (std::getline(fin, line))
{
std::istringstream iss(line);
int grade;
while (iss >> grade)
{
grades.push_back(grade);
}
}
Here's a somewhat more compact and elegant method, using istream-iterators and back-inserters:
#include <iterator>
#include <algorithm>
// other headers as before
std::vector<int> grades;
for (std::string line; std::getline(fin, line); )
{
std::istringstream iss(line);
std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(),
std::back_inserter(grades));
}
The point is that formatted token extraction (>>) from streams does already precisely what you want to, and it subsumes both tokenizing and parsing into integers.
The istream_iterator encapsulates the token extraction and allows you to treat a stream as if it were already a sequence of parsed tokens. The copy algorithm then simply copies this sequence into a vector, inserting it at the end of the container.
If you use strtol or strtod, it gives you back a pointer to the end of what you just processed, so you can continue from there. No tokenization necessary :)
But it sounds like your problem is that you're reading the wrong line. Print out the gradeList variable before you start parsing.
You should end up with something like this:
fin.getline(grade_text, 200);
const char* grade = grade_text;
const char* next_grade;
double grade_sum = 0;
for( int grade_count = 0; grades[grade_count] = strtol(grade, &next_grade, 10), next_grade > grade; ++grade_count )
grade_sum += grades[grade_count];
double mean_grade = grade_sum / grade_count;

What's the correct way to read a text file in C++?

I need to make a program in C++ that must read and write text files line by line with an specific format, but the problem is that in my PC I work in Windows, and in College they have Linux and I am having problems because of line endings are different in these OS.
I am new to C++ and don't know could I make my program able read the files no matter if they were written in Linux or Windows. Can anybody give me some hints? thanks!
The input is like this:
James White 34 45.5 10 black
Miguel Chavez 29 48.7 9 red
David McGuire 31 45.8 10 blue
Each line being a record of a struct of 6 variables.
Using the std::getline overload without the last (i.e. delimiter) parameter should take care of the end-of-line conversions automatically:
std::ifstream in("TheFile.txt");
std::string line;
while (std::getline(in, line)) {
// Do something with 'line'.
}
Here's a simple way to strip string of an extra "\r":
std::ifstream in("TheFile.txt");
std::string line;
std::getline(input, line));
if (line[line.size() - 1] == '\r')
line.resize(line.size() - 1);
If you can already read the files, just check for all of the newline characters like "\n" and "\r". I'm pretty sure that linux uses "\r\n" as the newline character.
You can read this page: http://en.wikipedia.org/wiki/Newline
and here is a list of all the ascii codes including the newline characters:
http://www.asciitable.com/
Edit: Linux uses "\n", Windows uses "\r\n", Mac uses "\r". Thanks to Seth Carnegie
Since the result will be CR LF, I would add something like the following to consume the extras if they exist. So once your have read you record call this before trying to read the next.
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
If you know the number of values you are going to read for each record you could simply use the ">>" method. For example:
fstream f("input.txt" std::ios::in);
string tempStr;
double tempVal;
for (number of records) {
// read the first name
f >> tempStr;
// read the last name
f >> tempStr;
// read the number
f >> tempVal;
// and so on.
}
Shouldn't that suffice ?
Hi I will give you the answer in stages. Please go trough in order to understand the code.
Stage 1: Design our program:
Our program based on the requirements should...:
...include a definition of a data type that would hold the data. i.e. our
structure of 6 variables.
...provide user interaction i.e. the user should be able to
provide the program, the file name and its location.
...be able to
open the chosen file.
...be able to read the file data and
write/save them into our structure.
...be able to close the file
after the data is read.
...be able to print out of the saved data.
Usually you should split your code into functions representing the above.
Stage 2: Create an array of the chosen structure to hold the data
...
#define MAX 10
...
strPersonData sTextData[MAX];
...
Stage 3: Enable user to give in both the file location and its name:
.......
string sFileName;
cout << "Enter a file name: ";
getline(cin,sFileName);
ifstream inFile(sFileName.c_str(),ios::in);
.....
->Note 1 for stage 3. The accepted format provided then by the user should be:
c:\\SomeFolder\\someTextFile.txt
We use two \ backslashes instead of one \, because we wish it to be treated as literal backslash.
->Note 2 for stage 3. We use ifstream i.e. input file stream because we want to read data from file. This
is expecting the file name as c-type string instead of a c++ string. For this reason we use:
..sFileName.c_str()..
Stage 4: Read all data of the chosen file:
...
while (!inFile.eof()) { //we loop while there is still data in the file to read
...
}
...
So finally the code is as follows:
#include <iostream>
#include <fstream>
#include <cstring>
#define MAX 10
using namespace std;
int main()
{
string sFileName;
struct strPersonData {
char c1stName[25];
char c2ndName[30];
int iAge;
double dSomeData1; //i had no idea what the next 2 numbers represent in your code :D
int iSomeDate2;
char cColor[20]; //i dont remember the lenghts of the different colors.. :D
};
strPersonData sTextData[MAX];
cout << "Enter a file name: ";
getline(cin,sFileName);
ifstream inFile(sFileName.c_str(),ios::in);
int i=0;
while (!inFile.eof()) { //loop while there is still data in the file
inFile >>sTextData[i].c1stName>>sTextData[i].c2ndName>>sTextData[i].iAge
>>sTextData[i].dSomeData1>>sTextData[i].iSomeDate2>>sTextData[i].cColor;
++i;
}
inFile.close();
cout << "Reading the file finished. See it yourself: \n"<< endl;
for (int j=0;j<i;j++) {
cout<<sTextData[j].c1stName<<"\t"<<sTextData[j].c2ndName
<<"\t"<<sTextData[j].iAge<<"\t"<<sTextData[j].dSomeData1
<<"\t"<<sTextData[j].iSomeDate2<<"\t"<<sTextData[j].cColor<<endl;
}
return 0;
}
I am going to give you some exercises now :D :D
1) In the last loop:
for (int j=0;j<i;j++) {
cout<<sTextData[j].c1stName<<"\t"<<sTextData[j].c2ndName
<<"\t"<<sTextData[j].iAge<<"\t"<<sTextData[j].dSomeData1
<<"\t"<<sTextData[j].iSomeDate2<<"\t"<<sTextData[j].cColor<<endl;}
Why do I use variable i instead of lets say MAX???
2) Could u change the program based on stage 1 on sth like:
int main(){
function1()
function2()
...
functionX()
...return 0;
}
I hope i helped...