The good news is you have collected the right pieces of the puzzle to do what it is you are attempting to do. The bad news is ... you put the puzzle together wrong ... (but not terribly so).
The key is to systematically program each step you need to accomplish to read the csv file into a 2D array. (you should be using std::vector<std::vector<int>>
, but I suspect the 2D array is an assignment requirement -- and it is also good to know how to handle arrays of fundamental types -- there are a lot out there in legacy code). Up through the point of reading each line from the file and populating the stringstream
and then looking to parse with getline
using the ','
as a delimiter everything looks okay.
What you are missing are protections to prevent reading more columns than you have storage for and more rows than you have declared. While this will not impact your read of the values from you file if your file contains exactly the number of values as you have declared for your array, it is vital for using arrays of fundamental types correctly. They are of fixed size and there is no auto-allocation that will save you if you attempt to write beyond the bounds of your array (you will just corrupt your program stack)
How do you protect your arrays bound? Simple, just check the the current row doesn't exceed the number allocated and the same for the columns, e.g.
/* read each line, protect row bounds */
while (r < row && getline (inputfile,line)) {
int c = 0; /* declare c local to loop so it is reset each iteration */
std::string num;
std::stringstream ss (line);
/* loop reading from stringstream, protect column bounds */
while (c < col && getline (ss, num, ',')) {
try { /* try/catch exception handling required for stoi conversion */
array[r][c++] = std::stoi (num); /* convert, increment col count */
}
catch (const std::exception & e) {
std::cerr << "error: invalid conversion " << e.what() << '\n';
return 1;
}
}
if (c != col) { /* validate col number of values read & stored */
std::cerr << "error: invalid number of columns '" << c << "' row: "
<< r << '\n';
return 1;
}
r++; /* increment row count */
}
Also note the use of try/catch
exception handling. When using std::stoi
that is the only manner you have to validate the conversion, see cppreference.com - std::stoi and note the Exception heading detailing the two exception (std::invalid_argument
and std::out_of_range
) that must be handled. You cannot simply guess that all input files will be in the needed format with the correct values and hope for the best -- you must validate every input.
This applies to the number of columns in each row as well (and the number of rows filled when your read-loop is done). When you are done reading from the stringstring
, you need to validate the number of column-values read is the number expected and that you have not encountered a short row. Note: these are the minimum-validations needed. You are free to write additional validations such as to check to ensure the stringstring
is empty and that additional valued do not remain unread (not critical to the operation of the code, but may flag an invalidly formatted input file)
Lastly, while the remainder of the code simply outputs the values, take a look at Why is “using namespace std;” considered bad practice?. Develop good habits early. That also means Don't Hardcode Filenames. The arguments to main()
provide a way to pass needed information into your program on startup -- use them, or at minimum, prompt for the filename to open.
Further, always compile with warnings enabled. That means -Wall -Wextra -pedantic -Wshadow
for gcc/clang and for VS /W3
. Including -Wshadow
you would find that you shadow the previous declaration for variable c
in for (int c=0; c<col; c++)
Putting it altogether, you could do something similar to:
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#define ROW 4 /* while const int is fine, if you #define your constants */
#define COL ROW /* you have one single location at the top to make changes */
int main (int argc, char **argv) {
const int row = ROW, col = COL; /* optional, you can just use ROW & COL */
int array[row][col], r = 0;
std::string line;
if (argc < 2) { /* validate at least 1 argument given for filename */
std::cerr << "error: insufficient arguments\nusage: ./prog filename\n";
return 1;
}
std::ifstream inputfile (argv[1]); /* open file provided as 1st argument */
if (!inputfile.is_open()) { /* use std::cerr for error output, handle error */
std::cerr << "error: file open failed '" << argv[1] << "'.\n";
return 1;
}
/* read each line, protect row bounds */
while (r < row && getline (inputfile,line)) {
int c = 0; /* declare c local to loop so it is reset each iteration */
std::string num;
std::stringstream ss (line);
/* loop reading from stringstream, protect column bounds */
while (c < col && getline (ss, num, ',')) {
try { /* try/catch exception handling required for stoi conversion */
array[r][c++] = std::stoi (num); /* convert, increment col count */
}
catch (const std::exception & e) {
std::cerr << "error: invalid conversion " << e.what() << '\n';
return 1;
}
}
if (c != col) { /* validate col number of values read & stored */
std::cerr << "error: invalid number of columns '" << c << "' row: "
<< r << '\n';
return 1;
}
r++; /* increment row count */
}
if (r < row) { /* validate row number of arrays stored in 2D array */
std::cerr << "error: invalid number of rows '" << r << "'\n";
return 1;
}
for (r = 0; r < row; r++) { /* loop outputting results */
for (int c = 0; c < col; c++)
std::cout << " " << array[r][c];
std::cout << '\n';
}
}
(note: the #define
statements are optional, but provide a convenient place at the top of your code to adjust your constants if the need arises)
Example Use/Output
While your input file in dat/2darr.csv
, you would execute and receive the following output:
$ ./bin/read2darrcsv dat/2darr.csv
2 4 5 6
3 7 5 3
4 8 4 2
6 7 3 0
Let me know if you have further questions.