I've spent almost 4 hours trying to get past this issue...
I have a text file with over 100 rows. Each row has 4 values separated by commas. I want to be able to extract each value and save it into a variable (v1...v4).
I have used a for loop, as I won't be reading the entire contents of the file. I'm just trying to get 1 working for now.
So far, I have managed to read a single row. I just need to break the row up now. This is for my Uni assignment, and I am not allowed to use any boost or tokeniser classes. Just getline and other basic commands.
I have this code:
// Read contents from books.txt file
ifstream theFile("fileName.txt");
string v1, v2, v3, v4, line;
for (int i = 0; i < 1; i++) {
getline(theFile, line, '\n');
cout << line << endl; // This part works fine
getline(line, v1, ","); // Error here
cout << v1 << endl;
getline(line, v2, ","); // Error here
cout << v2 << endl;
getline(line, v3, ","); // Error here
cout << v3 << endl;
getline(line, v4, '\n'); // Error here
cout << v4 << endl;
}
theFile.close();
The error I get is - error: no matching function for call to ‘getline(std::string&, std::string&, const char [2])
How can I fix this?
The delimiter for getline is a character. You have used double-quote marks "," which represent a string (hence why the compiler error indicates that you've used char[2] - string literals contain an additional 'nul' character).
Single character values are represented using single quotes instead:
getline(myFile, v1, ',');
edit - I've just noticed you're passing a string as the first parameter, which getline doesn't support (it won't let you retrieve tokens directly from a string). You probably wanted to stuff the string into a stringstream instead
#include <sstream>
// etc ...
std::string csv = "the,cat,sat,on,the,mat";
std::istringstream buffer( csv );
std::string token;
while( std::getline( buffer, token, ',' ) )
{
std::cout << token << std::endl;
}
According to this page, you have to call getline with std::istream as the first parameter.
But line is of type std::string. You should pass a std::istream instead, which can
be done by creating an std::istringstream from line. This could work:
string v1, v2, v3, v4, line;
istringstream line_stream;
for (int i = 0; i < 1; i++) {
getline(theFile, line, '\n');
cout << line << endl;
line_stream.str(line); // set the input stream to line
getline(line_stream, v1, ","); // reads from line_stream and writes to v1
...
If you want to read more about std::istringstream, you can do that here.
Also, according to the above page, the third parameter has to be of type char. But you pass "," which will automatically converted to const char[2], a string. For characters, you use 'c'.
There are two overloads for std::getline:
istream& getline ( istream& is, string& str, char delim );
istream& getline ( istream& is, string& str );
Three of your calls pass a literal string constant as the third parameter, where a single char is required. Use ' rather than " for character constants.
string filename, text, line;
vector<string> v1;
vector<string> v2;
vector<string> v3;
vector<string> v4;
int i = 0, k = 0, n;
cout << "Enter filename." << endl;
cin >> filename;
cout << "How many lines of text are in the file?" << endl;
cin >> n;
cin.ignore(200, '\n');
ifstream file(filename.c_str());
if (!file) {
cerr << "No such file exists." << endl;
exit(1);
}
cin.ignore(200, '\n');
if (file.is_open()) {
while (file.good()) {
for (k = 0; k < n; k++) { //Loops for as many lines as there are in the file
for (i = 0; i < 4; i++) { //Loops for each comma-separated word in the line
getline(cin, text, ',');
if (i == 0)
v1.push_back(text);
else if (i == 1)
v2.push_back(text);
else if (i == 2)
v3.push_back(text);
else if (i == 3)
v4.push_back(text);
}
}
}
}
file.close();
return 0;
}
Related
For some reason the full lines from my input file are not reading into the array, only the first word in each line. I am currently using the getline call, but I am not sure why it is not working. Here is the what I have for the call to populate the array. The txt file is a list of songs.
const int numTracks = 25;
string tracks[numTracks];
int count = 0, results;
string track, END;
cout << "Reading SetList.txt into array" << endl;
ifstream inputFile;
inputFile.open("SetList.txt");
while (count < numTracks && inputFile >> tracks[count])
{
count++;
getline(inputFile, track);
}
inputFile.close();
while (count < numTracks && inputFile >> tracks[count])
The >> operator reads a single word. And this code reads this single word into the vector in question.
getline(inputFile, track);
True, you're using getline(). To read the rest of the line, after the initial word, into some unrelated variable called track. track appears to be a very bored std::string that, apparently, gets overwritten on every iteration of the loop, and is otherwise completely ignored.
Your loop is using the operator>> to read the file into the array. That operator reads one word at a time. You need to remove that operator completely and use std::getline() to fill the array, eg:
const int numTracks = 25;
std::string tracks[numTracks];
int count = 0;
std::cout << "Reading SetList.txt into array" << std::endl;
std::ifstream inputFile;
inputFile.open("SetList.txt");
while (count < numTracks)
{
if (!std::getline(inputFile, tracks[count])) break;
count++;
}
inputFile.close();
Or:
const int numTracks = 25;
std::string tracks[numTracks];
int count = 0;
std::cout << "Reading SetList.txt into array" << std::endl;
std::ifstream inputFile;
inputFile.open("SetList.txt");
while ((count < numTracks) && (std::getline(inputFile, tracks[count]))
{
count++;
}
inputFile.close();
Alternatively, consider using a std::vector instead of a fixed array, then you can use std::istream_iterator and std::back_inserter to get rid of the manual loop completely:
class line : public std::string {}
std::istream& operator>>(std::istream &is, line &l)
{
return std::getline(is, l);
}
...
std::vector<std::string> tracks;
std::cout << "Reading SetList.txt into array" << std::endl;
std::ifstream inputFile;
inputFile.open("SetList.txt");
std::copy(
std::istream_iterator<line>(inputFile),
std::istream_iterator<line>(),
std::back_inserter(tracks)
);
inputFile.close();
I have a code like this, concerning stringstream. I found a strange behavior:
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
int main()
{
int p, q;
fstream file;
string str;
stringstream sstr;
file.open("file.txt", ios::in);
if(file.is_open()) {
while(getline(file, str)) {
sstr << str;
sstr >> p >> q;
cout << p << ' ' << q << endl;
sstr.str("");
}
}
file.close();
return 0;
}
Suppose I have file.txt as
4 5
0 2
with return after 5 in the first line and 2 in the second line. The program gives me:
4 5
4 5
which means p and q are not correctly assigned. But I checked that each time sstr.str() with get the correct string of the line.
Why stringstream has a behaviour like this?
The stream is in a non-good state after reading the second integer, so you have to reset its error state before resuming.
Your real mistake was to not check the return value of the input operations, or you would have caught this immediately!
The simpler solution may be to not try to reuse the same stream, but instead make it anew each round:
for (std::string line; std::getline(file, line); )
{
std::istringstream iss(line);
if (!(iss >> p >> q >> std::ws) || !iss.eof())
{
// parse error!
continue;
}
std::cout << "Input: [" << p << ", " << q << "]\n";
}
When you read p, then q, you reach the end of your stream and the flag eofbit is set and you can't do anything anymore.
Just clear() it and your code will work as you expect.
But you may want to use directly file instead, and file.close(); will have a better place within your if:
fstream file;
file.open("file.txt", ios::in);
if(file.is_open()) {
int p, q;
while(file >> p >> q) {
cout << p << ' ' << q << endl;
}
file.close();
}
Your code has some redundant lines: fstream could be opened during the definition and no explicit file close() is needed, as it is automatically destroyed at the end of main().
Additionally, in your file reading loop, the line: sstr << str should be replaced with stringstream sstr(line); if you want to initialize a new stringstream for each line, which will make the line: sstr.str(""); redundant as well.
Applying the above corrections, here is your code:
int main() {
int p, q;
fstream file("file.txt", ios::in);
// check status
if (!file) cerr << "Can't open input file!\n";
string line;
// read all the lines in the file
while(getline(file, line)) {
// initialize the stringstream with line
stringstream sstr(line);
// extract line contents (see Note)
while (sstr >> p >> q) {
// print extracted integers to standard output
cout <<"p: " << p <<" q: "<< q << endl;
}
}
return 0;
}
Note: The line while (sstr >> p >> q) assumes that a line contains only integers, separated by white space.
I have a text file that displays the following:
John Smith 21 UK
David Jones 28 FRANCE
Peter Coleman 18 UK
and I am trying to strip each individual element into a vector array. I have tried using the getline function with a tab delimiter but it stores every element. For example:
getline (f, line, '\t');
records.push_back(line);
How can I seperate it line by line? The idea is to perform a search and output the corresponding line. A search for Jones will print out the second line for example.
This is what I have so far but as you can see, it's not giving me the desired outcome:
string sString;
string line;
string tempLine;
string str;
vector<string> records;
cout << "Enter search value: " << endl;
cin >> sString;
cout << "\nSEARCHING\n\n";
ifstream f("dataFile.txt");
while (f)
{
while(getline (f, tempLine))
{
getline (f, line, '\t');
records.push_back(line);
}
for(int i=0; i < records.size(); i++)
{
if(sString == records[i]) {
cout << "RECORD FOUND" << endl;
for(int j=0; j < records.size(); j++)
{
cout << j;
cout << records[j] << "\t";
}
}
}
}
f.close();
The first getline extracts a complete line from the input.
The second extracts one field from the next line. If you want
to recover the lines broken down into fields, you should do:
std::vector<std::vector<std::string>> records;
std::string line;
while ( std::getline( f, line ) ) {
records.push_back( std::vector<std::string>() );
std::istringsream fieldParser( line );
std::string field;
while ( std::getline( fieldParser, field ) ) {
records.back().push_back( field );
}
}
This will result in a vector of records, where each record is
a vector of fields. More often, you would want to use a struct
for the record, and do a bit more parsing on the line, e.g.:
struct Field
{
std::string firstName;
std::string lastName;
int age;
std::string country;
};
std::vector<Field> records;
std::string line;
while ( std::getline( f, line ) ) {
std::istringsream fieldParser( line );
Field field;
fieldParser >> field.firstName >> field.lastName >> field.age >> field.country >> std::skipws;
if ( !fieldParser || fieldParser.get() != EOF ) {
// Error occurred...
} else {
records.push_back( field );
}
}
(Something this simple will only work if none of the fields may
contain white space. But it's simple to extend.)
You are doing getline into tempLine which eats a whole line, then you are doing a different getline in the loop as well. That's a big part of why it doesn't work--you are simply throwing away tempLine which contains a lot of your data.
I have a file that looks like this:
Mike 1200
John 350
Jen 1500
Tara 700
Michelle 2000
Kevin 500
Matt 450
Kim 200
My code to store the contents:
#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
using namespace std;
const int MAX = 15;
int main() {
// declare variables
string names[MAX];
string tempscore;
float scores[MAX];
fstream infile;
infile.open("winners.txt", ios::in);
int cc = 0;
getline(infile, names[cc], ' ');
infile.ignore( 0, ' ');
infile >> tempscore;
infile.ignore( 1, '\n');
scores[cc] = strtof(tempscore.c_str(), NULL);
cout << "'" << names[cc] << "'" << endl;
cout << "'" << scores[cc] << "'" << endl;
int i = 1;
while (infile) {
getline(infile, names[i], ' ');
infile.ignore( 0, ' ');
infile >> tempscore;
infile.ignore( 1, '\n');
scores[cc] = strtof(tempscore.c_str(), NULL);
cout << "'" << names[i] << "'" << endl;
cout << "'" << scores[i] << "'" << endl;
i++;
}
infile.close();
return 0;
}
Most of the names are stored correctly but none of the scores are. Why? What am I doing wrong?
Is this the best way to do what I am trying to achieve?
There is a typo in your code. scores[cc] = strtof(tempscore.c_str(), NULL); should be scores[i] = strtof(tempscore.c_str(), NULL); in the while loop.
A few side notes:
There is no need to handle index 0 separately
You should check your array bounds in the while loop. Consider using vector if possible
As stefaanv has pointed out in the comment below, you are not checking the return value of getline. If you want to keep you code as is without making use of stringstream consider changing the while condition to while(i < MAX && getline(infile, names[i], ' ')) (and ofc, getting rid of subsequent getline call. Please note that it is important that i < MAX check is before getline otherwise there is a possibility of segmentation fault as names[i] is used in getline)
If you are using C++11, consider using stof instead of strtof as it operates directly on string and there is no need to get C-str for it
Hope this helps!
For linebased file parsing, first read the line and put it in a stringstream, then read out each field directly from that stringstream, without storing it in a string and converting.
int main() {
const int MAX = 15;
// declare variables
std::string names[MAX];
float scores[MAX];
std::fstream infile;
infile.open("winners.txt", std::ios::in);
int i = 0;
std::string line;
while (getline(infile, line)) {
std::stringstream input(line);
input >> names[i] >> scores[i];
if (input) {
std::cout << "'" << names[i] << "'" << std::endl;
std::cout << "'" << scores[i] << "'" << std::endl;
i++;
if (i >= MAX) break; // protect your array or use a vector or deque
}
}
infile.close();
return 0;
}
If you are able to use getline twice, do getline(infile, names[cc], ' '); and getline(infile, score[cc], ' ');
Or format the text file like this:
Mike
1200
John
350
Jen
1500
etc.
In this case, every third line contains the score, started by 2 and names are the same but started by 1. Count 1, 4, 7, 10, etc. and 2, 5, 8, 11, etc. and you know, which are the names and which are the scores.
EDIT: it's not an answer to the main question, but may be a good implementation.
If you have that file format, you could do the following:
Read the line
Get the position of the first space character that you find (since that is your separator)
Split the string from that position.
Store the first half into your string array, and then store the output of the function atoi(secondHalf.c_str()) called with the second
half as parameter into your score array.
I would also change the score array to int instead of float if you are not using real numbers.
UPDATE:
This is an example code of what I am trying to tell you. I'm using vectors for names and scores here:
int i = 0;
for(i=0; i<line.size(); i++)
{
if(line[i] == ' ') break; //Then we have the position of the space char
}
names.push_back(line.substr(0,i));
scores.push_back(std::string(atoi(line.substr(i))));
You can use C functions to read formatted lines from the file :
FILE *fp = NULL;
int score = 0;
char name[20];
fp = fopen("winners.txt", "r");
if(!fp)
return -1;
while(tell(fp) != EOF) {
fscanf(fp, "%s %d", name, &score);
printf("Name : %s, Score : %d", name, score);
}
do not forget to #include <cstdio>
Those are the parts of the code I have:
ifstream inFile;
inFile.open("Product1.wrl");
...
if (!inFile.is_open()){
cout << "Could not open file to read" << endl;
return 0;
}
else
while(!inFile.eof()){
getline(inFile, line);
cout << line << endl; //this statement only to chech the info stored in "line" string
if (line.find("PointSet"))
inFile >> Point1;
}
The output shows me the same string over and over again. So this means that the cursor inside the file does not proceed and getline reads the same line.
What might be the problem of this odd behavior?
If this is relevant:
The file does open as a .txt file and contains the exact information I need.
Okay I figured the problem:
Even after first eteration the return value of line.find("PointSet")is: 429467295... while my line string contains only one letter "S". Why?
Change
while(!inFile.eof()){
getline(inFile, line);
to
while( getline(inFile, line) ) {
I don't know why people get bitten by eof() quite so often, but they do.
Mixing getline with >> is problematic, because the >> will leave a '\n' in the stream, so the next getline will come back empty. Change that to use getline as well.
if (line.find("PointSet")) isn't what you want either. find returns the position in the string, or std::string::npos if it wasn't found.
Also, you can change
ifstream inFile;
inFile.open("Product1.wrl");
to
ifstream inFile ("Product1.wrl");
Here's a version showing the reads:
class Point
{
public:
int i, j;
};
template <typename CharT>
std::basic_istream<CharT>& operator>>
(std::basic_istream<CharT>& is, Point& p)
{
is >> p.i >> p.j;
return is;
}
int main()
{
Point point1;
std::string line;
while(std::getline(std::cin, line))
{
std::cout << line << '\n'; //this statement only to chech the info stored in "line" string
if (line.find("PointSet") != std::string::npos)
{
std::string pointString;
if (std::getline(std::cin, pointString))
{
std::istringstream iss(pointString);
iss >> point1;
std::cout << "Got point " << point1.i << ", " << point1.j << '\n';
}
else
{
std::cout << "Uhoh, forget to provide a line with a PointSet!\n";
}
}
}
}