0

For example to add the following CSV data:

enter image description here

I am trying to add CSV file into a 2D array string vector and get the sum of each column. The following program didn't work properly,

vector<string> read_csv(string filename){

    vector<string> result;
    fstream fin;
    fin.open(filename, ios::in);

    if(!fin.is_open())
        throw std::runtime_error("Could not open file");

    std::string line, colname;
    int val;

    // Read the column names
    if(fin.good())
    {
        std::getline(fin, line);
        std::stringstream ss(line);
        while(std::getline(ss, colname, ',')){
            result.push_back(colname);
            cout << colname << endl;
        }
    }

    while(std::getline(fin, line))
    {
        std::stringstream ss(line);
        int colIdx = 0;
        while(ss >> val){

            if(ss.peek() == ',') ss.ignore();
            colIdx++;
        }
    }
    fin.close();
    return result;
}

when I tried to go through the vector, I didn't get a proper result. It showed only the column names.

for (int i = 0; i < vectorCsv.size(); ++i) 
{
        cout << vectorCsv[i] << endl;
}

I couldn't find whether the error is in read_csv() function or in the forloop. Thank you for looking at this problem.

Shehan V
  • 164
  • 1
  • 14
  • I highly recommend using a library, parsing strings(and files) is very tedious in C++ – const_ref Feb 20 '20 at 14:48
  • 1
    @const_ref Libraries are great, but I imagine this person is in the learning phase (judging from the code and question), so this is a beneficial exercise, in my humble opinion – LLSv2.0 Feb 20 '20 at 14:51
  • What do you really add to the vector you return? – Some programmer dude Feb 20 '20 at 14:51
  • 2
    *I couldn't find whether the error is in read_csv() function or in the forloop* -- Are you using a debugger? Finding where the error is what you should be able to do -- fixing the error is another story. – PaulMcKenzie Feb 20 '20 at 14:52
  • 1
    Also remember that you attempt to read integer values, but your second reading loop will only read the first number of the date and time, and then `ss >> val` will fail. – Some programmer dude Feb 20 '20 at 14:53
  • @LLSv2.0 reinventing the wheel seems to me to be an exercise in futility – const_ref Feb 20 '20 at 14:53
  • @Some programmer dude i need to add data to vector from csv, then get the sum of each column from the vector. – Shehan V Feb 20 '20 at 14:53
  • Why are you showing us an image of a spreadsheet? Why not put the actual data in the question as text? – PaulMcKenzie Feb 20 '20 at 14:55
  • I mean, what does the code you *show* add to the vector? You only have one set of `push_back` into the vector. Where do you have those? What do those add to the vector? – Some programmer dude Feb 20 '20 at 15:00

3 Answers3

2

In your while loop, you never pushed any values to your vector.

It looks like you have everything you need to read the csv into a vector right here. Only problem is you stopped at column names.

// Read the column names
    if(fin.good())
    {
        std::getline(fin, line);
        std::stringstream ss(line);
        while(std::getline(ss, colname, ',')){
            result.push_back(colname);
            cout << colname << endl;
        }
    }

Try changing the code I copied above to:

// Read the column names
    while(std::getline(fin, line))
    {
        std::getline(fin, line);
        std::stringstream ss(line);
        while(std::getline(ss, colname, ',')){
            result.push_back(colname);
            cout << colname << endl;
        }
    }
LLSv2.0
  • 501
  • 6
  • 20
  • i used result.at(colIdx).push_back(val); but that didn't work. – Shehan V Feb 20 '20 at 14:57
  • are you trying to load the CSV into a 2D array? a vector of vectors (i.e. a separate vector for each columns) – LLSv2.0 Feb 20 '20 at 14:59
  • Is it important that you hold onto the column names (the best solution sort of depends on it)? If you just want to sum the columns, you can use your first loop to get all the column names and count how many columns there are (we'll call this variable N). Then you can create a vector of N vectors. Then you can do a for loop for N iterations which does `std::getline(ss, colname, ',')` and pushes the result to the the ith vector. Make sense? – LLSv2.0 Feb 20 '20 at 15:11
2
  1. Don't try to create vectors of std::strings, that's probably not very efficient - each string being allocated and de-allocated separately.
  2. Don't read CSV's yourself - you're reinventing the wheel. Use an existing library. Here's a question about finding one at Software Recommendations StackExchange:

    Modern C++ CSV reader (and maybe writer) library

einpoklum
  • 118,144
  • 57
  • 340
  • 684
2

I cannot believe that we are using a library for such an ultra simple thing like splitting a std::string into tokens.

C++ has, since long, a build in and dedicated functionality, specifically designed for this purpose, to tokenize strings (split strings into tokens). And because such a simple dedicated function, designed for this purpose, is available, it simply should be used. There is no need for external libraries or complicated constructs. Simply use the std::sregex_token_iterator.

This is an iterator (like many other iterators), that iterates over tokens (sub-strings) of a string. So, what we want.

We can then use the std::vectors range constructor to write something simple like this:

std::vector tokens(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {}));

So, we define a variable with the name "tokens" of type std::vector (with CTAD the type of the vector is automatically deduced). We use its range constructor and provide a begin and an end iterator. The begin iterator is the std::sregex_token_iterator and the end-iterator is its default-initialized counterpart.

To put such a vector into a 2D Vector, we use the outer vectors emplace_back function and do an inplace construction for the inner vector.

So you read the whole CSV-File with 2 statements

  • a simple for loop
  • a simple emplace back with the std::sregex_token_iterator
        // We will read all lines of the source file with a simple for loop and std::getline
        for (std::string line{}; std::getline(csvFile, line); ) {

            // We will split the one big string into tokens (sub-strings) and add it to our 2D array
            csvData.emplace_back(std::vector<std::string>(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {}));
        }

So, why should you use a library for such a simple task that you can do with 2 statements? I personally fail to understand that. Therefore, I find that the advise in the accepted answer is flat wrong. But, to avoid starting religious discussions: This is my very personal humble opinion and everybody can do what he wants.

Please see a complete working example, which solves your problem, with just a few lines of code . . .

#include <iostream>
#include <fstream>
#include <vector>
#include <regex>

const std::string csvFileName{ "r:\\csv.csv" };
const std::regex delimiter{ "," };

int main() {

    // Open the file and check, if it could be opened
    if (std::ifstream csvFile(csvFileName); csvFile) {

        // This is our "2D array string vector" as described in your post
        std::vector<std::vector<std::string>> csvData{};


        // Read the complete CSV FIle into a 2D vector ----------------------------------------------------
        // We will read all lines of the source file with a simple for loop and std::getline
        for (std::string line{}; std::getline(csvFile, line); ) {

            // We will split the one big string into tokens (sub-strings) and add it to our 2D array
            csvData.emplace_back(std::vector<std::string>(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {}));
        }
        // -------------------------------------------------------------------------------------------------


        // This is for summing up values
        double DP{}, Dta{}, Dts{};

        // Iterate in a simple for loop through all elements of the 2D vector, convert the vlaues to double and sum them up
        for (size_t i = 1U; i < csvData.size(); ++i) {

            DP += std::stod(csvData[i].at(1));
            Dta += std::stod(csvData[i].at(2));
            Dts += std::stod(csvData[i].at(3));
        }

        // Sho the result to the user
        std::cout << "\nSums:  DP: " << DP << "  Dta: " << Dta << "  Dts: " << Dts << "\n";
    }
    else { // In case that we could not open the source file
        std::cerr << "\n*** Error. Could not open file " << csvFileName << "\n\n";
    }
    return 0;
}

But as said, everybdoy can do whatever he wants.

A M
  • 14,694
  • 5
  • 19
  • 44