henry groves
henry groves

Reputation: 887

How to iterate through a csv file and then split each column into a vector

So I have a CSV with 2 columns - One for A song and one for the artist of that song. For example

The Pretender,Foo Fighters
In Bloom,Nivana
Champagne Supernova,Oasis
Starlight,Muse
Will We Talk?,Sam Fender

I am converting a python program that I wrote a while back into c++. In that program it uses the csv library to read the csv, and choose a random row from that. It then splits it into two columns and then stores them in variables, answer being the song name and title being the song title:

    #select a random row and store it in a list
    randomrow = random.choice(list(csvreader))
    #print(randomrow)
    answer = randomrow[0]
    answer = answer.strip()
    #print(answer)

    track = randomrow[0]
    #print(track)
    #split track into two columns - title and artist
    title = track.split()

I have tried techniques from this thread: here

such as:

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

std::ifstream songsCsv("Songs.csv");

std::string line;
while (std::getline(songsCsv line))
{
    std::istringstream iss(line);
    std::string song, artist;
    if (!(iss >> song >> artist)) { break; }

    cout << song;
    cout << artist;

}

however, nothing was outputted here. If anyone had any ideas on how to do this tey would be much appreciated.

Upvotes: 1

Views: 699

Answers (2)

Papipone
Papipone

Reputation: 1123

According to your CSV file, you could have a solution like this :

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

int main()
{
    std::vector<std::pair<std::string, std::string>> result;
    std::string line;
    std::string first, second;
    std::ifstream ifs("Songs.csv");

    while (std::getline(ifs, line))
    {
        std::istringstream iss(line);
        std::getline(iss, first, ',');
        std::getline(iss, second, ',');
        result.push_back(std::make_pair(first, second));
    }

    for (std::vector<std::pair<std::string, std::string>>::const_iterator it = result.cbegin(); it != result.cend(); ++it)
    {
        std::cout << it->first << " : " << it->second << '\n';
    }

    return 0;
}

A safer solution, if you have more than 2 columns (more than one song), would be to iterate over the line retrieved from your CSV file and append it to a std::map<std::string, std::vector<std::string>> where the key is the name of the band.

Edit

Here is another solution which uses an std::map where the first member is the name of the band. This one is preferred if you have more than two columns (more than one song) and if the last column of your CSV file is the name of the band.

For instance, you could have the following CSV file :

The Pretender,AAAA,Foo Fighters
In Bloom,BBBB,Nivana
Champagne Supernova,CCCC,Oasis
Starlight,DDDD,Muse
Will We Talk?,EEEE,FFFF,Sam Fender
#include <sstream>
#include <string>
#include <fstream>
#include <iostream>
#include <map>
#include <vector>

int main()
{
    std::map<std::string, std::vector<std::string>> result;
    std::string line;
    std::ifstream ifs("Songs.csv");
    int i = 0;

    while (std::getline(ifs, line))
    {
        std::istringstream iss(line);
        std::vector<std::string> temp;
        for (std::string token; std::getline(iss, token, ','); )
        {
            temp.push_back(token);
        }

        result[temp[temp.size() - 1]].insert(result[temp[temp.size() - 1]].end(), std::make_move_iterator(temp.begin()), std::make_move_iterator(temp.end() - 1));
    }

    for (std::map<std::string, std::vector<std::string>>::const_iterator it = result.cbegin(); it != result.cend(); ++it)
    {
        std::cout << it->first << " : ";

        for (std::vector<std::string>::const_iterator vit = it->second.cbegin(); vit != it->second.cend(); ++vit)
        {
            std::cout << *vit << ' ';
        }

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

Upvotes: 3

cmdLP
cmdLP

Reputation: 1856

The problem is that >> only splits words around whitespace (newline, tabs, spaces).

Inspired by this question you can change the delimiter to a comma like this:

#include <locale>
#include <iostream>


struct comma_is_space : std::ctype<char> {
  comma_is_space() : std::ctype<char>(get_table()) {}
  static mask const* get_table()
  {
    static mask rc[table_size];
    rc[','] = std::ctype_base::space;  // split on commas
    return &rc[0];
  }
};

{
    ...
    std::istringstream iss(line);
    iss.imbue(std::locale(iss.getloc(), new comma_is_space()));
    ...
}

Upvotes: 0

Related Questions