0

so I'm trying to read in some data from a csv file into my program, but I only want some specific columns. I know there is a lot of stuff online regarding this but I can't seem to fit any of them to my requirements.

Firstly, I have created a struct that takes in Date and Time (class objects) and a Speed value from a file, and store it in Vector template class I have made myself. My main program right now works correctly for a test file like this:

31/12/2013 11:45,55.6

Date and time is read together, and then separated, and it works fine. Now here is the actual file:

WAST,DP,Dta,Dts,EV,QFE,QFF,QNH,RF,RH,S,SR,ST1,ST2,ST3,ST4,Sx,T
31/03/2016 09:00,14.6,175,17,0,1013.4,1016.9,1017,0,68.2,6,512,22.7,24.1,25.5,26.1,8,20.74
31/03/2016 09:10,14.6,194,22,0.1,1013.4,1016.9,1017,0,67.2,5,565,22.7,24.1,25.5,26.1,8,20.97
31/03/2016 09:20,14.8,198,30,0.1,1013.4,1016.9,1017,0,68.2,5,574,22.7,24,25.5,26.1,8,20.92

What I want to do is extract details from the WAST column which is date and time, and then the speeds from the S column. I have a generic idea of how to do it i.e: Extract the whole first line into a string and ignore everything other than WAST and S, or something like that. But even then, how am I supposed to get every value under only the columns I want to? Please help.

Main.cpp:

#include <iostream>
#include <string>
#include <fstream>
#include "Date.h"
#include "Time.h"
#include "Vector.h"

using namespace std;

typedef struct {

        Date d;
        Time t;
        float speed;

}WindLogType;

int main()
{

Date dTest;
Time tTest;
float speedtest = 52.5;

Vector<WindLogType> windlog;


ifstream infile("testinput2.csv");

if(!infile){

    cout << "File not found.";

    return -1;

};

WindLogType windlog2;

//int i = 0;

while(!infile.eof()){

    infile >> windlog2.d >> windlog2.t >> windlog2.speed;


    windlog.add(windlog2);

}

for(int i = 0; i < windlog.size(); i++){

    cout << windlog[i].d << " " << windlog[i].t << " Speed: " << windlog[i].speed << endl;

}


infile.close();

return 0;

}
thedafferg
  • 99
  • 6
  • 1
    For each line, extract it into a string, [split it by using the comma as a seperator](https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c) and store the columns into an array of strings. Now all there's left to do is to access the wanted indexes (columns) in that array. In addition, [there are better ways to read a CSV file](https://stackoverflow.com/questions/1120140/how-can-i-read-and-parse-csv-files-in-c). – SubMachine May 02 '20 at 13:21
  • 1
    Avoid this patern: `while(!infile.eof()){` [https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons) – drescherjm May 02 '20 at 13:22
  • Okay, @SubMachine I managed to actually get that to work for me, thank you. I just have a small issue now, if looping till the endOfFile is not suitable, how would I make a loop that loops until newline, stores that whole line, and repeats that process till the end of file? Because right now my output is only of the first row of details – thedafferg May 02 '20 at 14:02
  • @thedafferg Did you look at the second URL in my previous answer? It's all there – SubMachine May 02 '20 at 14:36

1 Answers1

1

What you can do here is write a stream extraction operator for your struct WindLogType that you use to read the file one line at a time, extracting the information you need from the line and populating the struct.

std::getline can be used to not only read a whole line, but also tokenise your string using a delimiter.

Here's something you can try with some comments to explain:

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

using Date = std::string;
using Time = std::string;

typedef struct {
    Date d;
    Time t;
    float speed;
} WindLogType;

std::istream& operator >> (std::istream& is, WindLogType& sl) {

    // Read a complete line
    std::string line;
    if (std::getline(is, line)) {

        auto iss = std::istringstream{line};

        // Read date from line (up to space)
        std::getline(iss, sl.d, ' ');
        // Read time (up to ',')
        std::getline(iss, sl.t, ',');

        std::string speed;
        // Read speed (up to next comma)
        std::getline(iss, speed, ',');
        sl.speed = std::stof(speed);
    }

    return is;
}

int main()
{
    std::ifstream infile("testinput2.csv");

    if (!infile) {
        std::cout << "File not found.";
        return -1;
    };

    Vector<WindLogType> windlog;
    std::string str;
    std::getline(infile, str); // skip the first line

    WindLogType windlog2;
    while (infile >> windlog2) {
        windlog.add(windlog2);
    }

    return 0;
}

The stream extraction operator (>>) reads a line from the file. With that line it creates a std::istringstream object from it and then extracts the relevant information using std::getline.

I don't know how your Date and Time types are defined. I have just aliased them as std::string. You will have to do your own processing of the strings yourself.

Hope this is useful.


Edit:

The reason why you can't do while(infile >> windlog2.d >> windlog2.t >> windlog2.speed) as it that >> reads up to a white space/newline. Your data is bit complex: it's comma separated and you have parse the date and time fields. The good thing with the approach above is that it allows you to stream data directly into a WindLog structure.

If you really don't want to have a stream extraction operator for your type you can use the same code. It will be like this:

std::string line;
std::getline(infile, line); // skip the first line

while (std::getline(infile, line)) {
    WindLogType sl;
    auto iss = std::istringstream{ line };

    // Read date from line (up to space)
    std::getline(iss, sl.d, ' ');
    ...
}

Personally I prefer having a stream extraction operator for a type as it encapsulates all the reading operation in one function, but if you want to do some other processing then you can take the second approach.

jignatius
  • 5,606
  • 2
  • 7
  • 21
  • This is very useful, thank you very much. I want to avoid overloading the input stream operator however if possible, and the only doubt I have is what condition can I put so that it loops through a line, does all my getline operations and then move on to the next line until the final line in the file? infile.eof is not appropriate apparently so I'm not sure what other conditions I can use. Can I do something like while(infile >> windlog2.d >> windlog2.t >> windlog2.speed) or is that not possible? – thedafferg May 03 '20 at 06:25