Reputation: 71
I'm quite new to C++ and I have a question about reading text file data.
I have a text file that contains data set like this:
The Undertaker 4 3 2 6
John Cena 22 19 8 5
Kurt Angle 5 9 33 17
and the code I'm using to read it is
for(int i=0; i<numWrestlers; i++)
{
getline(infile, firstName, " ");
getline(infile, lastName, " ");
for(j=1; j<4; i++)
{
getline(infile, score[i], " ")
}
}
but occasionally the file will have lines that look like:
Rob Van Dam 45 65 35 95
Hitman Bret Hart 34 9 16
Hulk Hogan 9
I don't know how to handle these entries. Any help would be appreciated, if this is a repeat question please link the original. Thanks
Upvotes: 7
Views: 5262
Reputation: 393799
Here's that Spirit approach that people have been clamouring for suggested:
namespace grammar {
using namespace x3;
auto name = raw [ +(!int_ >> lexeme[+graph]) ];
auto record = rule<struct _, Record> {} = (name >> *int_);
auto table = skip(blank) [record % eol];
}
The trick is to accept words as part of the name until the first data value (!int_
does that part).
The record rule parses into Record
:
struct Record {
std::string name;
std::vector<int> data;
};
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iostream>
namespace x3 = boost::spirit::x3;
struct Record {
std::string name;
std::vector<int> data;
};
BOOST_FUSION_ADAPT_STRUCT(Record, name, data)
namespace grammar {
using namespace x3;
auto name = raw [ +(!int_ >> lexeme[+graph]) ];
auto record = rule<struct _, Record> {} = (name >> *int_);
auto table = skip(blank) [record % eol];
}
int main()
{
std::istringstream iss(R"(The Undertaker 4 3 2 6
John Cena 22 19 8 5
Rob Van Dam 45 65 35 95
Hitman Bret Hart 34 9 16
Hulk Hogan 9
Kurt Angle 5 9 33 17)");
std::vector<Record> table;
boost::spirit::istream_iterator f(iss >> std::noskipws), l;
if (parse(f, l, grammar::table, table)) {
for (auto& r : table) {
std::copy(r.data.begin(), r.data.end(), std::ostream_iterator<int>(std::cout << r.name << ";", ";"));
std::cout << "\n";
}
}
}
Prints
The Undertaker;4;3;2;6;
John Cena;22;19;8;5;
Rob Van Dam;45;65;35;95;
Hitman Bret Hart;34;9;16;
Hulk Hogan;9;
Kurt Angle;5;9;33;17;
Upvotes: 6
Reputation: 996
You could follow the thought process outlined below
std::string line = "";
while(getline(file, line))
{
//index = first occurrence of a digit
//split the line at index - 1
//left_line = names, right_line = numbers
//further processing can now be done using the left_line and right_line
}
Upvotes: 2
Reputation: 49028
std::getline
sets failbit
if it didn't read any characters for whatever reason.
So, infile.fail()
will return true
if failbit
was set, which would mean that it didn't read the values correctly (if they're not there for example).
Upvotes: 0
Reputation: 409442
Read the whole line, look for the first digit and split the line into two substrings just before the first digit. The first substring is the name and the second substring contains the numbers.
How you handle names with more than two "words" in them is up to you, but it's not so easy as it might seem since some middle names aren't actually proper middle names but part of the last name (as in the example of "Rob Van Dam").
The numbers are easier, especially if you use a std::vector
to store them instead of a fixed-size array, then you can just use read the integers from an std::istringstream
using the normal >>
input operator in a loop, and push back into the vector.
Upvotes: 2