Jan Müller
Jan Müller

Reputation: 423

Parse string of numbers into vector of structs

I want to parse a string of numbers into a vector of elements. The string consists of blocks of four numbers, separated by ( ) : /, and each block is separated by a ;.

Specifically, the string is in this format: int(int):float/float;, see code sample below. I think I could use a regular expression, but since the data is so structured, I'm sure there must be a more approachable and easier way to parse such a string. I'm using istringstream, but it feels a bit clumsy.

std::string line = "0(0):0/0;1(2):0.01/0.02;2(4):0.02/0.04;3(6):0.03/0.06;"
struct Element {
  int a;
  int b;
  int c;
  int d;
};

std::vector<Element> = parse(line);


std::vector<Element> parse(std::string line)
{
  std::vector<Element> elements;
  std::istringstream iss(line);
  while(iss) {
    char dummy;
    Element element;

    iss >> element.a;
    iss.read(&dummy,sizeof(dummy)); // (
    iss >> element.b;
    iss.read(&dummy,sizeof(dummy)); // )
    iss.read(&dummy,sizeof(dummy)); // :
    iss >> element.c;
    iss.read(&dummy,sizeof(dummy)); // /
    iss >> element.d;
    iss.read(&dummy,sizeof(dummy)); // ;

    if (!iss) {break;}

    elements.push_back(element);
  }
  return elements;
}

My questions:

  1. What would be a good way to parse? Should I use std::stringstream and read in number by number and 'chop off' the characters in between? As done in the code sample?
  2. This code has a bug and attempts to read one extra set of values, because while(iss) is still true, after the last character has been read in. How to terminate this loop without testing after each iss>>? Or more generally, how to loop over extractions from istringstream?

Upvotes: 1

Views: 481

Answers (1)

Bob__
Bob__

Reputation: 12749

Your data are well structured, you can easily overload operator>> to extract the class members from an std::ifstream and then keep reading them from an istringstream or a file stream.

Here is a possible implementation:

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
#include <iterator>
#include <stdexcept>

class Element
{
public:
    Element() {}
    Element(int aa, int bb, float cc, float dd) : a{aa}, b{bb}, c{cc}, d{dd} {}

    friend std::istream &operator>> (std::istream &in, Element &e);
    friend std::ostream &operator<< (std::ostream &out, Element const &e);

private:
    int a;
    int b;
    float c;
    float d;
};

std::istream &operator>> (std::istream &in, Element &e)
{
    char delimiter;
    if ( not ( in >> e.a >> delimiter  and  delimiter == '(' and
               in >> e.b >> delimiter  and  delimiter == ')' and
               in >> delimiter         and  delimiter == ':' and
               in >> e.c >> delimiter  and  delimiter == '/' and
               in >> e.d >> delimiter  and  delimiter == ';' )
         and not in.eof() )
    {
        in.setstate(std::ios_base::failbit);
    }
    return in;

}

std::ostream &operator<< (std::ostream &out, Element const &e)
{
    return out << e.a << '(' << e.b << "):" << e.c << '/' << e.d << ';';
}

std::vector<Element> read_Elements_from(std::istream &in)
{
    std::vector<Element> tmp (
        std::istream_iterator<Element>{in},
        std::istream_iterator<Element>{}
    );
    if ( not in.eof() )
        throw std::runtime_error("Wrong format");

    return tmp;
}

int main()
{
  try
  {
    using std::cout;
    std::istringstream iss {
        "0(0):0/0;1(2):0.01/0.2;2(4):0.02/0.04;3(6):0.03/0.06;"
    };

    auto els_s = read_Elements_from(iss);

    cout << "Elements read from the string:\n";
    for ( auto const &i : els_s )
    {
        cout << i << '\n';
    }

    // assuming a file which lines are similar to the string provided
    std::ifstream input_file {"input_data.txt"};
    if ( not input_file )
        throw std::runtime_error("Can't open input file");

    auto els_f = read_Elements_from(input_file);

    cout << "\nElements read from the file:\n";
    for ( auto const &i : els_f )
    {
        cout << i << '\n';
    }
  }
  catch ( std::exception const &e )
  {
      std::cerr << "\nAn unexpected problem cause this application to end:\n\n"
                << e.what() << ".\n\n";
      return EXIT_FAILURE;
  }
}

Upvotes: 1

Related Questions