2

I am trying to read a csv file and assigning the values to a 2d array but i got weird results and some garbage values. Although the first row was correct, the second and third row become weird.

Below is the code:

#include "pch.h"
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    ifstream myFile;
    myFile.open("test.csv");

    int _data[3][3];
    int i = 0;
    int j = 0;

    while (myFile.good())
    {
        string line;
        getline(myFile, line, ',');

        _data[i][j] = stoi(line);
        j++;
        if (j > 3) {
            i++;
            j = 0;
        }
    }

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            cout << _data[i][j] << " ";
        }
        cout << endl;
    }
}

And I create a csv file which has following datas in it:

1,1,1
1,2,3
3,1,3

The output result i got from code was:

1 1 3
3 1 3
-858993460 -858993460 -858993460

I am trying to see if my loops went wrong or not but it seems fine to me.

1

3 Answers 3

3

You are making things much more difficult on yourself by using a fixed array instead of a vector of vector<int> for your 2D array. Additionally, for parsing a .csv file, reading each complete line, and then creating a stringstream from the line and parsing with getline using ',' terminator and then using stoi to convert the field to integer value (C++11) makes the process quite simple.

For example, taking the filename to read as the first argument to the program, you could implement the above as:

#include <iostream>
#include <fstream>
#include <sstream>

#include <string>
#include <vector>

using namespace std;

int main (int argc, char **argv) {

    string line;                    /* string to hold each line */
    vector<vector<int>> array;      /* vector of vector<int> for 2d array */

    if (argc < 2) { /* validate at least 1 argument given */
        cerr << "error: insufficient input.\n"
                "usage: " << argv[0] << " filename\n";
        return 1;
    }

    ifstream f (argv[1]);   /* open file */
    if (!f.is_open()) {     /* validate file open for reading */
        perror (("error while opening file " + string(argv[1])).c_str());
        return 1;
    }

    while (getline (f, line)) {         /* read each line */
        string val;                     /* string to hold value */
        vector<int> row;                /* vector for row of values */
        stringstream s (line);          /* stringstream to parse csv */
        while (getline (s, val, ','))   /* for each value */
            row.push_back (stoi(val));  /* convert to int, add to row */
        array.push_back (row);          /* add row to array */
    }
    f.close();

    cout << "complete array\n\n";
    for (auto& row : array) {           /* iterate over rows */
        for (auto& val : row)           /* iterate over vals */
            cout << val << "  ";        /* output value      */
        cout << "\n";                   /* tidy up with '\n' */
    }
    return 0;
}

(note: the automatic memory management provided by string and vector will allow you to read an array of any size (up to the limits of your virtual memory) without knowing the number of fields or rows beforehand. You can add simple counters for validating each row contains an equal number of values, etc...)

Example Input File

$ cat file.txt
1,1,1
1,2,3
3,1,3

Example Use/Output

$ ./bin/iostream_sstream_csv_stoi file.txt
complete array

1  1  1
1  2  3
3  1  3

Look things over and let me know if you have further questions.

Sign up to request clarification or add additional context in comments.

4 Comments

Hi David, thanks for you help. I have edited your code to suit the IDE I am using(visual studio) because I am not using commands to compile my code and it works. The reason that I didn't use vector was because I am still new to C++ and only have C knowledge and I am still considering myself as a beginner. I will try to read some tutorials about vector first. Thanks again!
@nickydemon just press Ctrl + F5
@nickydemon - I too have a C background. That's why I hinted as just thinking of a vector<vector<int>> as a vector of vector<int> just as int array[3][3] is just an array [3] of int array[3] -- but with vector you use row.push_back(val) to add a value to a row and then array.push_back(row) to add the row to the array. Same concept but with automatic memory management :)
Lastly, if you are using VS, I encourage you to open the "Developer Command Prompt" and compile from the command line. 10 times faster than setting up a project per-source for simple programs (or hacking a json file to allow compiling directly from a directory). Just open the Dev command prompt (set a decent font and size the terminal to your liking). All you do to compile is cl /nologo /W3 /Ox /FeName.exe /Tp source.cpp and your executable will be in Name.exe (or whatever name you give it following the /Fe option). cl.exe is the compiler VS uses. See cl /? for all options.
2

One problem:

if (j > 3) 

should be j == 3 because 3 is not a valid index.

You main problem is with:

getline(myFile, line, ',');

this misses the last number of a line because it does not end with a comma.

2 Comments

Thank you for replying. My output seems better but still i have garbage values on third row, and second row still have wrong values assign to it.
added to the answer, you need to change the way you read lines.
0

I've answered a similar set of problems here and here. As I have stated before; when parsing simple files it is typically easier to get the contents first. Then once you have all of the information you needed from the file you will typically store that contents into either a string, some stream or a buffer. After it is stored then you will want to close the file as you are done with it. Then after you have closed file handle this is when you want to parse that information and convert it into the data types that is required. With this kind of approach it is easier to create a user defined structure that will represent the data you are retrieving from the file. Then it is a matter of writing functions that will help you with the process of doing all of this work. Each function should have its own responsibility and task. I'll show you my version of the code with the data example you provided from your CSV file.

test.txt

1,1,1
1,2,3
3,1,3
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <exception>

struct Vector {
    int x;
    int y;
    int z;
    Vector() = default;
    Vector( int xIn, int yIn, int zIn ) :
        x( xIn ), y( yIn ), z( zIn ) 
    {}
};

std::vector<std::string> splitString( const std::string& s, char delimiter ) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream( s );
    while( std::getline( tokenStream, token, delimiter ) ) {
        tokens.push_back( token );
    }

    return tokens;
}

// NOTE: This function is not being used however
// it shows how to read a single line from the beginning of the file.
std::string getLineFromFile( const char* filename ) {
    std::ifstream file( filename );
    if( !file ) {
        std::stringstream stream;
        stream << "failed to open file " << filename << '\n';
        throw std::runtime_error( stream.str() );
    }

    std::string line;
    std::getline( file, line );

    file.close();

    return line;
}

void getDataFromFile( const char* filename, std::vector<std::string>& output ) {
    std::ifstream file( filename );
    if( !file ) {
        std::stringstream stream;
        stream << "failed to open file " << filename << '\n';
        throw std::runtime_error( stream.str() );
    }

    std::string line;

    while( std::getline( file, line ) ) {
        if ( line.size() > 0 ) 
            lines.push_back( line );
    }
    file.close();    
}

Vector parseDataAsVector( std::string& line ) {
    std::vector<std::string> tokens = splitString( line, ',' ); // Parse Line    
    return Vector( std::stoi( tokens[0] ), std::stoi( tokens[1] ), std::stoi( tokens[2] ) );
}

void generateVectors( std::vector <std::string>& lines, std::vector<Vector>& vectors ) {
    for( auto& l : lines ) {
        vectors.push_back( parseDataAsVector( l ) );
    }
    return vectors;
}

int main() {
    try {
        std::vector<std::string> fileConents; 
        getDataFromFile( "test.txt", fileContents );
        std::vector<Vector> data;
        generateVectors( fileConents, data );

        // test to see if info is correct
        for( auto& d : data ) {
            std::cout << d.x << " " << d.y << " " << d.z << '\n';
        }

    } catch( const std::runtime_error& e ) {
        std::cerr << e.what() << '\n';
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Console Output

1,1,1
1,2,3
3,1,3

This allows the code to be easier to debug, makes it readable, portable and reliable and in some ways even generic to some extent. There is one thing to be aware of with program above; it has to do with the implementation of the parsing function that pertains to the user defined data type Vector. You can have a text file that has more than 3 values followed by commas on the same line and it will still generate a vector based off of the first 3 values that it reads in from that line, the rest is ignored. The data is there in the vector of strings after the string is split and stored, but it's when we create our Vector using its constructor that we are only referencing the first three positions of that std::vector<std::string> to create and return it back to the caller. Also if you have more than three lines it will do as many lines as there are within the file as long as the data fits the current format of our data structure for parsing.

Comments

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.