user4075830
user4075830

Reputation: 87

User input to matrix in C++

I have trouble to read in an input from user and convert them into matrix for calculation. For example, with the input = {1 2 3 / 4 5 6}, the program should read in the matrix in the form of

1 2 3 
4 5 6

which have 3 cols and 2 rows. What i got so far which does not seem to work:

input.replace(input.begin(), input.end(), '/', ' ');
    stringstream ss(input);
    string token;   
    while (getline(ss, token, ' '))
    {
        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {
                int tok = atoi(token.c_str());
                (*matrix).setElement(i, j, tok);                
            }
        }
    }

So what I'm trying to do is to break the input into token and store them into the matrix using the setElement function which take the number of row, column and the variable that user want to store. What wrong with this code is that the variable of tok doesnt seem to change and keep stuck in 0. Assuming that row and col are knows.

Thanks so much for any help.

Upvotes: 0

Views: 4892

Answers (3)

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20730

Although many simple ways exist to solve the specific problem (and other answer have various good suggestions) let me try to give a more general view of the problem of "formatted input".

There are essentially three kind of problems, here:

  1. at low level you have to do a string to number conversion
  2. at a higher level you have to parse a composite format (understanding rows and line separation)
  3. finally you also have to understand the size of the compound (how many rows and cols?)

this 3 things are not fully independent and the last is needed to know how to store elements (how do you size the matrix?)

Finally there is a 4th problem (that is spread all other the other 3): what to do if the input is "wrong".

These kind of problem are typically afforded in two opposite ways:

  1. Read the data as they come, recognize if the format is matched, and dynamically grow the data structure that have to contain them or...
  2. Read all the data as once as they are (textual form), then analyze the text to figure out how many elements it has, then isolate the "chunks" and do the conversions.

Point 2. requires good string manipulations, but also requires the ability to know how the input is long (what happens if one of the separating spaces is a new-line? the idea the everything is got with a getline fails in those cases)

Point 1 requires a Matrix class that is capable to grow as you read or a temporary dynamic structure (like and std container) in which you can place what you read before sending it into the appropriate place.

Since I don't know how your matrix works, let me keep a temporary vector and counters to store lines.

#include <vector>
#include <iostream>
#include <cassert>

class readmatrix
{
    std::vector<int> data; //storage
    size_t rows, cols; //the counted rows and columns
    size_t col; //the counting cols in a current row

    Matrix& mtx; //refer to the matrix that has to be read
public:

    // just keep the reference to the destination
    readmatrix(Matrix& m) :data(), rows(), cols(), cols(), mtx(m)
    {}

    // make this class a istream-->istream functor and let it be usable as a stream 
    // manipulator: input >> readmatrix(yourmatrix) 
    std::istream& operator()(std::istream& s)
    {
        if(s) //if we can read
        {
            char c=0:
            s >> c; //trim spaces and get a char.
            if(c!='{') //not an open brace
            { s.setstate(s.failbit); return s; } //report the format failure
            while(s) //loop on rows (will break when the '}' will be found)
            {
                col=0;
                while(s) //loop on cols (will break when the '/' or '}' will be found)
                {
                    c=0; s >> c;
                    if(c=='/' || c=='}') //row finished?
                    { 
                        if(!cols) cols=col; //got first row length
                        else if(cols != col) //it appears rows have different length
                        { s.setstate(s.failbit); return s; } //report the format failure
                        if(c!='/') s.unget(); //push the char back for later
                        break; //row finished
                    }
                    s.unget(); //pushthe "not /" char back
                    int x; s >> x; //get an integer
                    if(!s) return s; //failed to read an integer!
                    ++col; data.push_back(x); //save the read data
                }
                ++rows; //got an entire row
                c=0; s >> c;
                if(c == '}') break; //finished the rows
                else s.unget(); //push back the char: next row begin
            }
        }
        //now, if read was successful,
        // we can dispatch the data into the final destination
        if(s)
        {        
            mtx.setsize(rows,cols); // I assume you can set the matrix size this way
            auto it = data.begin(); //will scan the inner vector
            for(size_t r=0; r<rows; ++r) for(size_t c=0; c<cols; ++c, ++it)
                mtx(r,c) = *it; //place the data
            assert(it == data.end()); //this must be true if counting have gone right
        }
        return s;
    }
};

Now you can read the matrix as

input >> readmatrix(matrix);

You will notice at this point that there are certain recurring patterns in the code: this is typical in one-pass parses, and those patterns can be grouped to form sub-parsers. If you do it generically you -in fact- will rewrite boost::spirit.

Of course some adaption can be done depending on how your matrix works (has it fixed sizes??), or what to do if rows sizes don't match (partial column filling ??)

You can even add a formatted input operator like

std::istream& operator>>(std::istream& s, Matrix& m)
{ return s >> readmatrix(m); }

so that you can just do

input >> matrix;

Upvotes: 1

Mart&#237;nV
Mart&#237;nV

Reputation: 184

If you know the size of the matrix on forehand you actually don't need getline, you should read int by int. (untested code)

input.replace(input.begin(), input.end(), '/', '\n');
stringstream ss(input);
for (int i = 0; i < row; i++)
{
    for (int j = 0; j < col; j++)
    {
        int tok;
        ss >> tok;
        (*matrix).setElement(i, j, tok);                
    }
}

Upvotes: 0

Xenogenesis
Xenogenesis

Reputation: 390

You are trying to operate on each cell of the matrix for each char read in the input! You have to take one char for each cell, not multiple.

Splitting a string in tokens can be done by using the following function. Please don't be shocked that the following code isn't runnable, this is due to the missing matrix class.

Try the following:

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

using namespace std;

void split(const string& str, char delimiter, vector<string>& result) {
   string::size_type i = 0;
   string::size_type delimOcc = str.find(delimiter);

   while (delimOcc != string::npos) {
       result.push_back(str.substr(i, delimOcc-i));
       i = ++delimOcc;
       delimOcc = str.find(delimiter, delimOcc);

      if (delimOcc == string::npos) {
         result.push_back(str.substr(i, str.length()));
      }
   }
}

int main()
{
    std::string input = "1 2 3 / 4 5 6";

    vector<string> rows;
    split(input, '/', rows);

    for(int i = 0; i < rows.size(); i++) {
        vector<string> cols;
        split(rows[i], ' ', cols);

        for(int j = 0; j < cols.size(); j++) {
            if(cols[j][0] != '\0'){
                int tok = stoi(cols[j]);
                (*matrix).setElement(i, j, tok);   
                cout << tok << " - " << i << " - " << j << endl;
            }
            else {
                if(j == 0) j--;
            }
        }
    }

    return 0;
}

Upvotes: 0

Related Questions