Since input data to applications never can be trusted there is an importance of adding error checks to see that the data provided is indeed valid (otherwise the result of the application might suffer from errors while parsing).
The "C++ way" of handling errors such as this is to throw an exception when a problem arise in the functions responsible for parsing data.
The caller of this function will then wrap the call in a try-catch-block to catch errors that might appear.
With a user-defined-type..
Defining your own type for holding your pairs of data will greatly improve the readability of your code, the output from the below implementation and the one found later in this post is the same.
#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>
struct Pair {
Pair (int a, int b)
: value1 (a), value2 (b)
{}
static Pair read_from (std::istream& s) {
int value1, value2;
if ((s >> std::ws).peek () != '(' || !s.ignore () || !(s >> value1))
throw std::runtime_error ("unexpected tokens; expected -> (, <value1>");
if ((s >> std::ws).peek () != ',' || !s.ignore () || !(s >> value2))
throw std::runtime_error ("unexpected tokens; expected -> , <value2>");
if ((s >> std::ws).peek () != ')' || !s.ignore ())
throw std::runtime_error ("unexpected token;expected -> )");
return Pair (value1,value2);
}
int value1, value2;
};
The one thing I've noticed that might be hard for programmers to grasp about the above is the use of s >> std::ws
; it's used to consume available white-spaces so that we can use .peek
to get the next non-whitespace character available.
The reason I implemented a static function read_from
instead of ostream& operator>>(ostream&, Pair&)
is that the later will require that we create an object before even reading from the stream, which in some cases are undesirable.
void
parse_data () {
std::string line;
while (std::getline (std::cin, line)) {
std::istringstream iss (line);
int N, M;
if (!(iss >> N >> M))
throw "unable to read N or M";
else
std::cerr << "N = " << N << ", M = " << M << "\n";
for (int i =0; i < M; ++i) {
Pair data = Pair::read_from (iss);
std::cerr << "\tvalue1 = " << data.value1 << ", ";
std::cerr << "\tvalue2 = " << data.value2 << "\n";
}
}
}
Normally I wouldn't recommend naming non-const variables in only uppercase, but to make it more clear which variable contains what I use the same name as your description of the input.
int
main (int argc, char *argv[])
{
try {
parse_data ();
} catch (std::exception& e) {
std::cerr << e.what () << "\n";
}
}
Without the use of user-defined-types
The straight forward method of parsing the data as well as having checks for errors is to use something as the following, though it could be greatly improved by using User Defined Objects and operator overloads.
- read each line using std::getline
- construct n std::istringstream iss (line) with the line read
- try to read two ints using iss >> N >> M
- read M "words" using a std::string s1* with iss >> s1;
- Construct a std::istringstream inner_iss using the s1 as initializer
- peek to see that the next char available is
(
&& ignore this char
- read integer
- peek to see that the next char available is
,
&& ignore this char
- read integer
- peek to see that the next char available is
)
&& ignore this char
If the stringstream isn't empty after step 4 or iss.good () returns false anywhere inbetween the steps the is a syntax error in the data read.
Sample implementation
The source can be found by following the link below (code put elsewhere to save space):
N = 0, M = 0
N = 2, M = 1
value1 = 0, value2 = 1
N = 2, M = 0
N = 5, M = 8
value1 = 0, value2 = 1
value1 = 1, value2 = 3
value1 = 2, value2 = 3
value1 = 0, value2 = 2
value1 = 0, value2 = 1
value1 = 2, value2 = 3
value1 = 2, value2 = 4
value1 = 2, value2 = 4