6

I have a program that take commands from user and it will process different commands differently. For example:

ADD_STUDENT ALEX 5.11 175
ADD_TEACHER MERY 5.4  120 70000
PRINT MERY 
REMOVE ALEX
PRINT TEACHER SALARY
PRINTALL 

therefore, I need to examine each line and see what is the input consists of.

Here is my code, but I think I misunderstand the way iss<< work. Can someone give me a suggestion? And tell me how why my code didn't work as I expected?

string line;
while(getline(cin, line))
{
  //some initialization of string, float variable
  std::istringstream iss(line);
  if(iss >> command >> name >> height >> weight)
   ..examine the command is correct(ADD_STUDENT) and then do something..
  else if(iss >> command >> name >> height >> weight >> salary)
   ..examine the command is correct(ADD_TEACHER) and then do something...
  else if(iss >> command >> name)
   ..examine the command is correct(REMOVE) and then do somethin...
}

My thought is that the iss>> first >>second >> third will return true if all arguments are filled and false if not enough arguments. But apparently I am wrong.

5 Answers 5

10

Your problem was deliciously underspecified. This always prompts me to supply an overblown example implementation using Boost Spirit.

Note: just don't hand this in as your homework assignment, please.

See it Live on Coliru with the following sample input:

ADD_STUDENT ALEX 5.11 175
ADD_STUDENT PUFF 6 7
ADD_STUDENT MAGIC 7 8
ADD_STUDENT DRAGON 8 9
ADD_TEACHER MERY 5.4  120 70000
PRINT MERY 
ADD_TEACHER DUPLO 5.4  120 140000
PRINTALL  10
REMOVE ALEX
PRINT  TEACHER SALARY
PRINT  MERY PUFF MAGIC DRAGON
REMOVE MERY PUFF MAGIC DRAGON
PRINT  TEACHER SALARY

Full code:


Update When including make_visitor.hpp as shown here you can write the visitor code more elegantly:

auto print_salary = [&] () 
{ 
    for(auto& p : names) 
        boost::apply_visitor(make_visitor(
                    [](Teacher const& v) { std::cout << "Teacher salary: " << v.salary << "\n"; },
                    [](Student const& v) {}), 
                p.second);
};

See adapted example Live on Coliru


#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

struct Person
{
    std::string name;
    double height, weight;
    friend std::ostream& operator<<(std::ostream& os, Person const& s) {
        return os << "Person { name:" << s.name << ", height:" << s.height << ", weight:" << s.weight << " }";
    }
};

struct Student : Person
{
    Student() = default;
    Student(std::string n, double h, double w) : Person {n,h,w} {}
};

struct Teacher : Person
{
    Teacher() = default;
    Teacher(std::string n, double h, double w, double s) : Person {n,h,w}, salary(s) {}
    double salary;
};

int main()
{
    std::stringstream ss;
    ss << std::cin.rdbuf();

    std::map<std::string, boost::variant<Student, Teacher> > names;

    using namespace qi;
    auto add_student  = phx::ref(names)[_1] = phx::construct<Student>(_1, _2, _3);
    auto add_teacher  = phx::ref(names)[_1] = phx::construct<Teacher>(_1, _2, _3, _4);
    auto remove       = phx::erase(phx::ref(names), _1);
    auto print_all    = [&] (int i) { for(auto& p : names) { std::cout << p.second << "\n"; if (--i==0) break; } };
    auto print_salary = [&] () 
    { 
        struct _ : boost::static_visitor<> {
            void operator()(Teacher const& v) const { std::cout << "Teacher salary: " << v.salary << "\n"; }
            void operator()(Student const& v) const { }
        } v_;
        for(auto& p : names) boost::apply_visitor(v_, p.second);
    };

    auto name_ = as_string[lexeme[+graph]];

    if (phrase_parse(begin(ss.str()), end(ss.str()), 
                (
                     ("ADD_STUDENT" >> name_ >> double_ >> double_)            [ add_student ]
                   | ("ADD_TEACHER" >> name_ >> double_ >> double_ >> double_) [ add_teacher ]
                   | (eps >> "PRINT" >> "TEACHER" >> "SALARY")                 [ print_salary ]
                   | ("PRINTALL" >> int_)      [ phx::bind(print_all, _1) ]
                   | ("PRINT"  >> +name_       [ std::cout << phx::ref(names)[_1] << std::endl ])
                   | ("REMOVE" >> +name_       [ remove ])
                ) % +eol,
                qi::blank))
    {
        std::cout << "Success";
    }
    else
    {
        std::cout << "Parse failure";
    }
}

Output:

Person { name:MERY, height:5.4, weight:120 }
Person { name:ALEX, height:5.11, weight:175 }
Person { name:DRAGON, height:8, weight:9 }
Person { name:DUPLO, height:5.4, weight:120 }
Person { name:MAGIC, height:7, weight:8 }
Person { name:MERY, height:5.4, weight:120 }
Person { name:PUFF, height:6, weight:7 }
Teacher salary: 140000
Teacher salary: 70000
Person { name:MERY, height:5.4, weight:120 }
Person { name:PUFF, height:6, weight:7 }
Person { name:MAGIC, height:7, weight:8 }
Person { name:DRAGON, height:8, weight:9 }
Teacher salary: 140000
Success
Sign up to request clarification or add additional context in comments.

1 Comment

@kbok Thank you. Although I intend this code rather jokingly. It is certainly the most complete answer. By a mile. Or seven. :)
7

Do it like so:

iss >> command;
if (!iss)
    cout << "error: can not read command\n";
else if (command == "ADD_STUDENT")  
    iss >> name >> height >> weight;
else if (command == "ADD_TEACHER")  
    iss >> name >> height >> weight >> salary;
else if ...

13 Comments

The == operator is illegal tho
It is not illegal for std::string. 2nd argument (c-string) is converted to std::string.
@user1701840 Provided that command is a std::string then it's legal.
@LeonidVolnitsky I don't think the c-string is converted, the operator simply has an overload bool operator== (std::string lhs, const char* rhs).
Usually, when !iss triggers, it's end-of-file. Make sure it doesn't get stuck in an infinite loop.
|
5

Your problem is that using the >> operator reads and clears a token from the stream.

if(iss >> command >> name >> height >> weight)

This (above) tries to read 4 tokens from the stream, and for every successful read, it clears the read data from the stream.

else if(iss >> command >> name >> height >> weight >> salary)

When you get to this (above) it means that some token could not be read and cast into the appropriate type, however it is likely that at least the command token is already stripped from the stream.

Comments

2

Well, it's too late for much chance at upvotes, but you guys got me thinking about this...

For robustness, you could split parsing into two stages: the first stage gets lines, and the second stage takes a line and does something with it.

For the first stage, you can use the getline:

#include <string>
#include <sstream>

void ParseLines(std::istream& source)
{
    while(source)
    {
        // Get a line from the source.
        std::string inputLine;
        std::getline(source, inputLine);

        // Make a stream out of it.
        std::istringstream inputStream(inputLine);
        std::string command;
        inputStream >> command;
        if(inputStream) // Empty or bad line: skip
            HandleCommand(command, inputStream);
    }
}

The second stage handles the command. It could be something direct like this:

void HandleCommand(const std::string& command, std::istringstream& params)
{
    if(command == "ADD_STUDENT")
    {
        float someFloat;
        int someInt;
        params >> someFloat >> someInt;
        // add the student.
    }
    // etc.
}

But I have no shame and would implement a Factory paradigm:

#include <map>

typedef void (*CommandHandler)(const std::string&, std::istringstream&);
typedef std::map<std::string, CommandHandler> CommandTable;

static CommandTable gCommands; // Yep. A global. Refactor however you see fit.

void HandleCommand(const std::string& command, std::istringstream& params)
{
    CommandTable::iterator handler = gCommands.find(command);
    if(handler == gCommands.end())
    {
        // Handle "command not found" error.
        return;
    }

    (*(handler->second))(command, params);
}

void AddStudent(const std::string& command, std::istringstream& params)
{
    float someFloat;
    int someInt;
    params >> someFloat >> someInt;
    // add the student.
}

// Other command handling functions here...

void RegisterCommands()
// Call this once prior to parsing anything,
// usually one of the first things in main().
{
    gCommands["ADD_STUDENT"] = &AddStudent;
    // ... other commands follow...
)

Haven't tested any of this, but it should be mostly there. Note any bugs in the comments.

P.S. This is highly inefficient and will run slower than a properly designed command parser, however, it should be good enough for most jobs.

4 Comments

looks like a good idea than throwing everything inside the main! Thanks!
Hohum. My answer weighs almost exactly the same number of LoC as your answer shows. Only, mine doesn't do shortcuts: it actually implements the commands and types, storage and lookup :/ Oh, and I posted it even later. (I'm a sucker for this kind of task like you, apparently :))
Just no pleasing some people. I used function pointers to keep the answer simple, and I deliberately restricted to C++03 or so because I have no idea how old this guy's compiler is. Like I said, refactor any way you feel like because the right refactoring depends on how the rest of the code is organized, and none of that was posted. C++11 provides a lot better options. Also, I was deliberately avoiding Boost Spirit so as not to confuse the poor OP's mind; they're just trying to use istream's operator >>. BTW, thanks for the downvote in spite.
What spite? Your answer is bad. The OP didn't specify C++03 and even if he did, there is tr1::function and boost::function. As for the global variables, posting an answer using globals is bad. You could at least take a parameter or something.
1

You could technically tokenize the entire input line, but that seems a little too far out from your level. If you did want to go into it, there is a nice page and tutorial here that will help you use strtok().

If you do not want to go that method, you can individually parse through your list of commands. Say you have read into a string named "command".

if (command == "ADD_STUDENT")
{
    int weight, height, otherfield;
    cout << ">" << flush;
    cin >> weight >> height >> otherfield;
    //do something, like add them to the database
}

That seems like your best bet, although it is a lot of coding, it is probably easier for you to accomplish. You could get really into it and use format strings like this:

scanf("%s, %s %d, %f", lastname, firstname, age, height);

This way, the input would look like this:

ADD_STUDENT Doe, John 30, 5.6

2 Comments

You don't need to resort to scanf and strtok to tokenize lines. See stackoverflow.com/questions/53849/…
@Mike it really matters on what type of string you're using. I never use std::string, so <T>.split() wont work for me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.