0

I have a file that stores a number matrix of unknown shape in format like

-1,4,12,5.7
2.1,3,-10,3.3
7.1,1.11,12,10

I attempt to store the matrix in a dynamically allocated array because I cannot hard code the number of row and column. For this part, I used pointer-to-pointer and a demo is as below,

#include <iostream>
using namespace std;

int main()
{
    // Rather than user input, I need to change this part to deciding the shape of the matrix myself
    int row, col;
    cout << "Enter row number and column number, separated with a space:\n";
    cin >> row >> col;
    int** p_p_grid = new int* [row];
    for(int i = 0; i < row; i++)
    {
        p_p_grid[i] = new int[col];
    }

    // Fill in the entries
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            // (i + 1) * (j + 1) needs to be replaced by true entries in the matrix
            p_p_grid[i][j] = (i + 1) * (j + 1);
        }
    }
    return 0;
}

But what is an efficient way to decide the shape of a comma separated number block before assigning the number one by one? And also how do I import a CSV-structured matrix in C++? (For some reason, I don't want to use a vector type, so please focus on an array)

Nicholas
  • 2,560
  • 2
  • 31
  • 58
  • 2
    If you use `new[]` for dynamic arrays you do it the wrong way. The correct way is to use [`std::vector`](http://en.cppreference.com/w/cpp/container/vector). Then you don't have to worry about the number of rows and columns, it will be fully dynamic. You could even have rows with different number of columns. That's it really, if you need dynamic array you use `std::vector`. What you "want" or not doesn't really matter IMO, the right answer is vectors. – Some programmer dude Oct 02 '16 at 18:01
  • Thanks, but is there a way if `vector` is not used?@JoachimPileborg – Nicholas Oct 02 '16 at 18:03
  • BTW, when you said `new[] for dynamic arrays you do it the wrong way`, do you just mean there are better methods than `new[]` or are you suggesting any mistakes in my code?@JoachimPileborg – Nicholas Oct 02 '16 at 18:06
  • You could read the file twice: First once to count the number of rows, and the number of columns in each row (if it could be different). Then allocate accordingly. And lastly read the actual data. Or you could read and reallocate as needed (will be messy). – Some programmer dude Oct 02 '16 at 18:16
  • For deciding shape you must first know all elements of array so you have to read all the elements in a row first. You should use vectors and declare it as `std::vector a(4)` where 4 are the numbers you found on some X row – JamesWebbTelescopeAlien Oct 02 '16 at 18:21
  • It can be assumed the number of elements in each row is the same@JoachimPileborg – Nicholas Oct 02 '16 at 18:26
  • `std::vector` does anything (or most of things) you could want. It's much safer and simple than doing everything manually, so why not use it? By the way, it is a bad idea to have a n-D arrays with non-contiguous memory (and vector> isn't better). Boost offers a [multidimensional array library](http://www.boost.org/doc/libs/1_61_0/libs/multi_array/doc/user.html). – asu Oct 02 '16 at 18:33
  • 1
    That simplifies it a little. Then you basically just count the number of lines, allocate accordingly, reset the file and read the actual data. Still doable, but using `std::vector` and [standard algorithms](http://en.cppreference.com/w/cpp/algorithm) (and [other utilities](http://en.cppreference.com/w/cpp/iterator)) will make it much simpler. – Some programmer dude Oct 02 '16 at 18:33

4 Answers4

1

what is an efficient way to decide the shape of a comma separated number block before assigning the number one by one?

Assuming you're reading from a file stream, the easiest way, would be to read the file twice: one for counting rows and commas, on for doing the real input.

Here an example of how to detect the end of the matrix, stoping when the number of elements of a new line don't match the format of the matrix:

int nrows=1, ncols=0;
string line; 
while (getline(ifs, line)) {
    int n=1; 
    for (auto x: line)   // count commas in the line 
        if (x==',') 
            n++; 
    if (!ncols) 
        ncols = n;        // first line sets th enumber of columns
    else if (n == ncols)  // subsequent lines increase the row count
        nrows++; 
    else break;          // unless the format does'n match anymore
}
ifs.clear();   // remove eof 
ifs.seekg (0, ifs.beg);  // rewind

Online demo

What causes lack of efficiency in this approach, is that you read the file twice. For this reason, by the way, you can't use this approach for reading cin: you can't rewind.

You can optimize this either by caching the lines read (but you'd need to manage a rowing array of strings, as you're not allowed to use vectors), or by letting the matrix grow dynamically (which no longer correspond to your question as this would not provide the matrix size upfront).

how do I import a CSV-structured matrix in C++

Within each line, just read the doubles, followed by a char (which should be the ','):

char forget_me; 
for (int i=0; i<nrows; i++) 
    for (int j=0; j<ncols; j++) { 
         cin >> p_p_grid[i][j];  
         if (j<ncols-1) 
            cin>>forget_me; 
    }
Christophe
  • 68,716
  • 7
  • 72
  • 138
1

I know you didn't want a vector-based solution, but here's one anyway

int main() {
    ifstream input("input.txt");
    if(!input) {
        cerr << "couldn't open file" << endl;
        exit(1);
    }

    double number;
    vector<vector<double>> matrix;
    vector<double> current_row;
    while(input >> number) { // loop once for each number in the file
        current_row.push_back(number);
        int next_char = input.get(); // should be ',' or '\n'
        if(next_char == '\n') {
            // current row is finished
            matrix.push_back(current_row);
            current_row.clear();
        }   
    }   

    // now print the matrix back out again
    for(auto const & one_row : matrix) {
        for(auto one_number : one_row) {
            cout << "\t," << one_number;
        }   
        cout << endl;
    }   
}
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
1

It a bit fiddly. Use either C++ iostream or C stdio.h, and read in a whole line. So if using getline / fgets, you need a very big buffer, say 8k. Now for the first line, parse into fields. For a first attempt, simply count the commas, but the actual rules are more complicated than that.

The go through line by line, extracting the data. Since you've no say over the number of rows, you've no choice other than to dynamically grow the buffer for each line. An STL vector makes this easy for you - just push it back and the buffer will grow. However if you want you can use this structure

int **p_p_grid = 0;
int Nrows = 0;
int Ncolumns = 0;

/* ( for first line, fill the number of columns) */
/* for each line */
p_p_grid = realloc((Nrows + 1) * sizeof(int *));
if(!p_p_grid) 
  memory_failure();
p_p_grid[Nrows] = malloc(Ncolums * sizeof(int));
if(!p_p_grid[Nrows])
  memory_failure();
for(i=0;i<Ncolumns;i++)
   p_p_grid[Nrows][i] = /* parse logic here */ 
Nrows++;
Malcolm McLean
  • 6,258
  • 1
  • 17
  • 18
0

As noted, I would use std::vector instead. Considering that each row have a fixed number of elements, I would also be using std::array. Perhaps doing something like this:

#include <vector>   // For std::vector
#include <array>    // For std::array
#include <string>   // For std::string and std::getline
#include <fstream>  // For std::ifstream
#include <sstream>  // For std::isstream

int main()
{
    std::vector<std::array<double, 4>> grid;

    std::ifstream input{"input.txt"};

    std::string line;

    // Outer loop reads the rows
    while(std::getline(input, line))
    {

        int i = 0;
        std::istringstream iss{line};

        std::array<double, 4> values;
        double value;

        // Inner loop extracts the values on each row
        while (iss >> value)
        {
            values[i] = value;

            // Skip over the comma
            iss.ignore(std::numeric_limits<std::streamsize>::max(), ',');
        }

        grid.push_back(values);
    }

    // Now print the values
    int row_number = 1;
    for (auto const& row : grid)
    {
        std::cout << "Row " << row_number++ << ": ";

        for (auto const value : row)
        {
            std::cout << value << ' ';
        }

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

Note that I haven't actually tested the code above. It compiles cleanly though.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621