Luke Boon
Luke Boon

Reputation: 71

C++ Reading Multiple Variables from Text Line

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

Answers (4)

sehe
sehe

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;
};

Full Demo Live

Live On Coliru

#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

Duly Kinsky
Duly Kinsky

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

Rakete1111
Rakete1111

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

Some programmer dude
Some programmer dude

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

Related Questions