0

I am creating a zoo record, I have a csv file containing the following points: animal_name,hair,feathers,eggs,milk,airborne,aquatic,predator,toothed,backbone,breathes,venomous,fins,legs,tail,domestic,catsize,class_type aardvark,1,0,0,1,0,0,1,1,1,1,0,0,4,0,0,1,1 antelope,1,0,0,1,0,0,0,1,1,1,0,0,4,1,0,1,1

I would only like to read the animal_name, predator and domestic value for all animals listed in the csv file. I have the following code:

#include "ZooRecord.hpp"

template <class T>
ZooRecord<T>::ZooRecord(std::string input_name_file)
{
    //Declare variables
    std::string name, none, predator, domestic;
    std::fstream in_file;

    //Open file
    in_file.open(input_name_file);
    if(in_file.fail())
    {
        std::cout << "Could not open file" << std::end;
    }
    else
    {
        while(in_file.good())
        {
            std::getline(in_file, name, ',');
            std::cout << name << " ";

            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');

            std::getline(in_file, predator, ',');
            std::cout << predator << " ";

            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');

            std::getline(in_file, none, ',');

            std::getline(in_file, domestic, ',');
            std::cout << domestic << " ";

            std::getline(in_file, none, ',');
            std::getline(in_file, none, ',');

        }
    }
}

Expected output: name predator domestic

devChrus
  • 1
  • 1
  • 2
  • Did you mean domestic instead of predator in your 3rd cout? – Eugene Sep 21 '19 at 03:38
  • Not your current problem, but `while(in_file.good())` isn't good enough, I'm afraid. All you learn here is the stream is good before you start reading. What you really care about is is the stream still good AFTER reading from it. A common result of this bug is the program reads all the data from the file and is still good, so it tries to read more data from the file, instantly fails because there is no more data to be read and invalid data from the failed reads is used by the program. What you need to do is read, test validity, then use. – user4581301 Sep 21 '19 at 03:38
  • Sidenote: `#include "ZooRecord.hpp>`, (beware the typo) suggests that the implementation of `ZooRecord` may not be in a header. This can lead to the problem described in [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file). If you have a chain of header files including one another, ignore me. – user4581301 Sep 21 '19 at 03:43

1 Answers1

0

What you want to do is to split a line with comma separated values. This task is also called tokenizing. And for tokenizing, the STL has a dedicated function. The std::sregex_token_iterator. With that, the task becomes really simple.

So, we will first create a proxy class, with which we can read a complete line and split it into tokens.

I will call it LineAsVector in my example. So, if you have an open filestream ifs and a std::vector of std::strings vs, then you could simply write: ifs >> vs; and you would read a comlete line and tokenize it.

In the example below, we will read the complete csv file into memory with a one line. During the definition of the variable std::vector<std::vector<std::string>> csv we will use the range constructor and read and split the complete file using:

std::vector<std::vector<std::string>> csv{ std::istream_iterator<LineAsVector>(csvFile), std::istream_iterator<LineAsVector>() };

That was easy. And you data that you are looking for can be found at vector index 0, 7, 15. If you are looking for the predator in the 2nd line, you can simply access that by writing csv[1][7]

And, at the end, we will display the data.

#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <sstream>
#include <iterator>
#include <algorithm>

std::istringstream csvFile{R"(animal1,hair,feathers,eggs,milk,airborne,aquatic,predator1,toothed,backbone,breathes,venomous,fins,legs,tail,domestic1,catsize,class_type,aardvark,1,0,0,1,0,0,1,1,1,1,0,0,4,0,0,1,1,antelope,1,0,0,1,0,0,0,1,1,1,0,0,4,1,0,1,1
animal2,hair,feathers,eggs,milk,airborne,aquatic,predator2,toothed,backbone,breathes,venomous,fins,legs,tail,domestic2,catsize,class_type,aardvark,1,0,0,1,0,0,1,1,1,1,0,0,4,0,0,1,1,antelope,1,0,0,1,0,0,0,1,1,1,0,0,4,1,0,1,1
animal3,hair,feathers,eggs,milk,airborne,aquatic,predator3,toothed,backbone,breathes,venomous,fins,legs,tail,domestic3,catsize,class_type,aardvark,1,0,0,1,0,0,1,1,1,1,0,0,4,0,0,1,1,antelope,1,0,0,1,0,0,0,1,1,1,0,0,4,1,0,1,1)"};


class LineAsVector {    // Proxy for the input Iterator
public:
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, LineAsVector& lv) {

        // Read a line
        std::string line; lv.completeLine.clear();
        std::getline(is, line);

        // The delimiter
        const std::regex re(",");

        // Split values and copy into resulting vector
        std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
            std::sregex_token_iterator(),
            std::back_inserter(lv.completeLine));
        return is;
    }

    // Cast the type 'CompleteLine' to std::string
    operator std::vector<std::string>() const { return completeLine; }
protected:
    // Temporary to hold the read vector
    std::vector<std::string> completeLine{};
};


int main()
{

    // Read the complete csv file and split it
    // Please note: instead of the istringstream csvFile, you can of course use any other open file stream
    std::vector<std::vector<std::string>> csv{ std::istream_iterator<LineAsVector>(csvFile), std::istream_iterator<LineAsVector>() };

    // Go through all lines of the csv data
    // And print the  0th, 7th, 15th value in each line
    std::for_each(csv.begin(), csv.end(), [](std::vector<std::string> & vs) {
        std::copy_if(vs.begin(), vs.end(), std::ostream_iterator<std::string>(std::cout, " "), [ui = -1](std::string & s) mutable {
            ++ui; return (ui == 0) || (ui == 7) || (ui == 15);
        });
        std::cout << "\n";
    });

    return 0;
}
A M
  • 14,694
  • 5
  • 19
  • 44