One of my input file, which looks like this:

Stroustrup, Bjarne  8   8   -1  -1  -1
Lovelace, Ada   1   60  14  43  -1
von Neumann, Jon    77  48  65  -1  -1
Wirth, Niklaus  51  59  -1  -1  -1
Wozniak, Steve  81  -1  -1  -1  -1
Babbage, Charles    31  92  -1  -1  -1
Hopper, Grace   76  -1  -1  -1  -1
Bird, Tweety    -99 -99 -99 -99 -99
Sylvester           77  39  -1  -1  -1

My current program streams the data using

infile >> lastName >> firstName >> ...

Unfortunately this only worked with the other input files because every line actually had a last and first name. Here, because of the two part last name in the third line and only the first name in the last line, the rest of the data fails to stream. Is there any method to grab a string from the beginning of the line until it reaches an integer?

有帮助吗?

解决方案

While parsing input files, you'll almost never end up with solution that directly uses original stream to fill your variables with values. Format of input might differ, errors might occur... Better approach in this case would be reading the input line by line and processing each line separately. While processing each line you can construct a temporary istringstream that you can use to read words from it and check whether the word is convert-able to number or not (if 0 is not valid value, use std::atoi):

std::string line;
while (std::getline(infile,line))
{
    if (line.empty()) continue;
    std::istringstream is(line);
    std::string word;
    while (is >> word)
    {
        int val = std::atoi(word);
        if (val)
        {
            // TODO: number
        }
        else
        {
            // word
        }
    }
}

Alternatively you might consider using std::isdigit to just check whether first character of word is digit or not:

if (std::isdigit(word[0])) ...

其他提示

Do you have any control over the format of your input file? Seeing as you both the option to have a two-part name (e.g., "von Neumann") or a single name (i.e., not first and last, e.g., "Sylvester"), it's going to be unnecessarily difficult to parse. If you could quote name, like "von Neumann", Jon then things may be a lot easier.

One approach may include this:

#define DELIM 'c'
ostringstream namestream;
infile.get(namestream, DELIM);
string name = namestream.str();

That will get the string from infile until the character DELIM is reached. Looking at your file, it looks like name parts and first/last names are separated by a space or comma followed by space, wherease the gap between the end of the name and the first number may be a tab. If that is the case, you can do this with tab ('\t') as your delimeter character.

Unfortunately, this approach only supports DELIM as a single character, not a set of characters. So you wouldn't be able to read up to the first digit (or "digiit or '-'" if you want to support negative numbers).

Here is a complete example program:

#include <iostream>
#include <string>
#include <locale>
#include <fstream>

template<
    class charT,
    class iter_type,
    class string_iterator_type
>
void basic_get_name(iter_type beg, iter_type end, std::ios_base& str,
                    string_iterator_type it1, string_iterator_type it2)
{
    auto ctypeFacet = &std::use_facet<std::ctype<charT>>(str.getloc());
    typedef std::ctype_base base_type;

    while (beg != end)
    {
        if (ctypeFacet->is(base_type::alpha | base_type::punct, *beg))
            *it1++ = *beg;
        else if (ctypeFacet->is(base_type::space, *beg))
        {
            if (ctypeFacet->is(base_type::alpha, *++beg))
            {
                while (ctypeFacet->is(base_type::alpha, *beg) && (beg != end))
                    *it2++ = *beg++;
                break;
            }
            break;
        }
        ++beg;
    }
}

template<class charT>
void get_name(std::basic_istream<charT>& is, std::basic_string<charT>& first,
                                             std::basic_string<charT>& last)
{
     typedef std::istreambuf_iterator<charT> iter_type;

     basic_get_name<charT>(iter_type{is}, iter_type{},
                           is, std::back_inserter(first),
                               std::back_inserter(last));
}

Here is how you would call it:

int main()
{
    std::string first, last;
    get_name(infile, first, last);

    std::cout << first << last;
}

You can even go as far as to create your own class that has both first and last name strings and create a facet for the extraction of the names into those data members.

And get_name should return the stream so that a check can be made on the stream state. As it stands now this code does not do that but it can be implemented through an argument of type ios_base::iostate and adding stream state error bits to it inside basic_get_name.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top