简体   繁体   中英

Reading a Matrix from .txt File into A Vector of Vectors With the Size of the Array Declared in the .txt File

For example, I generate a.txt file with the following text, the first number being the number of rows for my array and the second number being the number of columns in the array. I would like to write code that can be used for a matrix of any size, not just 3 rows and 4 columns and this is my primary problem, as I am unsure how to read a different number of terms from a line based on pre-input data.

3

4

1 2 3 4

5 6 7 8

9 0 1 2

( There is not spacer lines on the real.txt file but I used it for question formatting) I would like to store this array into a class and perform matrix operations on it but I cannot for the life of me figure out how to actually get the matrix into a vector of vectors to do anything to it. I have tried to use getline and use " myfile >> number " but I am really not this good at coding. Any assistance at all is appreciated.

First, and very important, you know already about the std::vector and even about 2d vectors, like std::vector<std::vector<int>> . That is very good and makes life simple.

You know also about C++ extraction operator >> and the inserter operator << . That is especially important for the proposed solution. You also know that these operators can be chained, because they always return a reference to the stream for which they are called. With that you can write things like

std::cout << numberOfRows << '\n' << numberOfColumns << '\n';

What will hapen here is:

First, std::cout << numberOfRows will be executed and return std::cout . The resulting statement will be

std::cout << '\n' << numberOfColumns << '\n';

Next, std::cout << '\n' will be executed and return std::cout . The resulting statement will be

std::cout << numberOfColumns << '\n';

And so on and so on. So, you can see that we can chain. Good.

For input using the extraction operator >> we know that it will, per default settings, skip all white spaces. So, to read the number of rows and number of columns from your source input, you may simply write:

is >> numberOfRows >> numberOfColumns;

and we know the size of the matrix.

I will later explain that we do not even need this, because we "see", looking at the source data, that we have 3 rows and 4 columns.

Anyway, now we have the matrix size and we can use it. With the initial defined empty 2d-vector, we cannot do that much, so let's resize it to the given values. This will be done with the std::vector s resize command. Like this:

data.resize(numberOfRows, std::vector<int>(numberOfColumns));

The interesting point is that the 2d-vector internally knows the number of rows and columns. You can get this with the size() function.

So, we could now read all data with a nested for loop:

for (unsigned int row = 0; row < m.numberOfRows; ++row)
    for (unsigned int col = 0; col < m.numberOfColumns; ++col)
        is >> m.data[row][col];

That is very simple and intuitive.

We can also use more modern range based for loops. Our vector knows its sizes internally and and simply iterate over all its data.

We just need to use references to be able to modify the data. So, we can also write:

 // The vector will now know its size. So, we can use range based for loops to fill it
for (std::vector<int>& row : data)          // Go over all rows
    for (int& col : row)                    // For each column in a row
        is >> col;                          // Read the value and put in matrix

This looks even simpler, and it works, because we use references.


Then, how to combine all this know how in a function. Luckily, C++ allows to overwrite the IO operators, eg the inserter operator >> for our custom data type, our class. We just need to add:

friend std::istream& operator >> (std::istream& is, Matrix& m) {

to our class and implement the functionality described above.

The whole program example could then look like:

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

struct Matrix {
    unsigned int numberOfRows{};
    unsigned int numberOfColumns{};

    std::vector<std::vector<int>> data{};
    
    // Read matrix from any stream
    friend std::istream& operator >> (std::istream& is, Matrix& m) {

        // First read the number of rows and columns
        is >> m.numberOfRows >> m.numberOfColumns;

        // Now resize the vector to have the given size
        m.data.clear();
        m.data.resize(m.numberOfRows, std::vector<int>(m.numberOfColumns));

        // The vector will now know its size. So, we can use range based for loops to fill it
        for (std::vector<int>& row : m.data)        // Go over all rows
            for (int& col : row)                    // For each column in a row
                is >> col;                          // Read the value and put in matrix
        return is;
    }
    // Write matrix to any stream
    friend std::ostream& operator << (std::ostream& os, const Matrix& m) {
        os << m.numberOfRows << '\n' << m.numberOfColumns << '\n';
        // Now output the matrix itself
        for (const std::vector<int>& row : m.data) {      // Go over all rows
            for (const int& col : row)                    // For each column in a row
                os << col << ' ';
            os << '\n';
        }
        return os;
    }
    void readFromFile(const std::string& filename) {
        // Open the file and check, if could be opened
        std::ifstream ifs{ filename };
        if (ifs) {
            // Read complete matrix with above extractor operator
            ifs >> *this;
        }
        else std::cerr << "\n*** Error: Could not open source file '" << filename << "' for reading\n";
    }
    void writeToFile(const std::string& filename) {
        // Open the file and check, if could be opened
        std::ofstream ofs{ filename };
        if (ofs) {
            // Read complete matrix with above extractor operator
            ofs << *this;
        }
        else std::cerr << "\n*** Error: Could not open source file '" << filename << "' for writing\n";
    }
};

// In this example, I will read from a stringstream, but reading from a file is the same
std::istringstream iss{ R"(
3
4
1 2 3 4
5 6 7 8
9 0 1 2
)" };


int main() {

    Matrix matrix{};
    // Read and parse the complete matrix
    iss >> matrix;

    // Debug output. Show matrix
    std::cout << matrix;
}

This looks already good and can be extended as needed.


You remember that I said that there is no need to specify the numbers of rows and columns, because we "see" it. But how can our program see that?

Simple.

We first read a complete line, with for example "1 2 3 4" in it, this is a row. Then we put this string into a std::istringstream and extract all values that we can get from their. This, we will do until end of file.

Very simple. Let me show you the modified example:

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

struct Matrix {
    std::vector<std::vector<int>> data{};

    // Read matrix from any stream
    friend std::istream& operator >> (std::istream& is, Matrix& m) {
        m.data.clear();
        std::string line{};
        // Read all lines, all rows of the matrix
        while (std::getline(is,line) and not line.empty()) {

            // Put this into a stringstream for further extraction
            std::istringstream iss{ line };

            // Add a new row to our 2d vector
            m.data.push_back({});

            // Now extract all values from the line and add it to the just created row
            int value{};
            while (iss >> value) {
                m.data.back().push_back(value);
            }
        }
        return is;
    }
    // Write matrix to any stream
    friend std::ostream& operator << (std::ostream& os, const Matrix& m) {
        // Now output the matrix itself
        for (const std::vector<int>& row : m.data) {      // Go over all rows
            for (const int& col : row)                    // For each column in a row
                os << col << ' ';
            os << '\n';
        }
        return os;
    }
    void readFromFile(const std::string& filename) {
        // Open the file and check, if could be opened
        std::ifstream ifs{ filename };
        if (ifs) {
            // Read complete matrix with above extractor operator
            ifs >> *this;
        }
        else std::cerr << "\n*** Error: Could not open source file '" << filename << "' for reading\n";
    }
    void writeToFile(const std::string& filename) {
        // Open the file and check, if could be opened
        std::ofstream ofs{ filename };
        if (ofs) {
            // Read complete matrix with above extractor operator
            ofs << *this;
        }
        else std::cerr << "\n*** Error: Could not open source file '" << filename << "' for writing\n";
    }
};

// In this example, I will read from a stringstream, but reading from a file is the same
std::istringstream iss{ R"(1 2 3 4
5 6 7 8
9 0 1 2
)" };


int main() {

    Matrix matrix{};
    // Read and parse the complete matrix
    iss >> matrix;

    // Debug output. Show matrix
    std::cout << matrix;
}

And with that you could even read assymetric matrices, so matrices, with a different number of columns in different rows.


Hope this helps. . .

You can use a 2D std::vector to store the information read from the input file. Using std::vector has the advantage that it makes the program flexible in that you won't need to know beforehand how many rows and columns will there be in the input file. That is, you don't need the first and second rows in your input files specifying the rows and columns. The program will work for arbitrary number of rows and columns. Moreover, the file can contain rows that have uneven entries. You can use the given program as a reference(starting point). The explanation is given as comments in the program.

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include<fstream>
int main() {
    std::string line;
    int word;

    
    std::ifstream inFile("input.txt");
    
    //create a 2D vector that will store the read information
    std::vector<std::vector<int>> vec;
    
    if(inFile)
    {   //read line by line
        while(getline(inFile, line, '\n'))        
        {
            //create a temporary vector that will contain all the columns
            std::vector<int> tempVec;
            
            std::istringstream ss(line);
            
            //read word by word(or int by int) 
            while(ss >> word)
            {
                //add the word to the temporary vector 
                tempVec.push_back(word);
            
            }      
            //now all the words from the current line has been added to the temporary vector 
            vec.emplace_back(tempVec);
        }    
    }
    else 
    {
        std::cout<<"file cannot be opened"<<std::endl;
    }
    
    inFile.close();
    
    //lets check out the elements of the 2D vector so the we can confirm if it contains all the right elements(rows and columns)
    for(std::vector<int> &newvec: vec)
    {
        for(const int &elem: newvec)
        {
            std::cout<<elem<<" ";
        }
        std::cout<<std::endl;
    }
}

Demo .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM