0

Right so here it is:

Text is supposed to be read from a csv file, divided into different strings and put in a vector/array. After that some typecasting, calculations and sorting is supposed to happen but I'm not that worried about that right now since it feels pointless if I can't even get past the first bit. I have read up on the topic and watched numerous videos and looked at examples about vectors, struct and csv but still can't get anything to work. I've tried to minimize the ambition to a more "make-it-happen"-mode from the initiating creating a struct that holds date, time, location, temp, humidity. Sorry about all the comments but it's helpful for me to keep the remainder of my head organized. I really want to be good at this so any feedback will be helpful. Thanks in advance. So here it is:

#include <iostream> <//För att kunna hantera input/output
#include <fstream> <//För att kunna läsa textfiler
#include <string> <//För att kunna använda sekvenser av bokstäver s.k. strängar.
#include <chrono> <//För att kunna mäta hur effektiv min kod är genom tidtagning.
#include <vector><//För att kunna använda vector-arrayer och göra koden mer effektiv.

using namespace std; <//För att förenkla kodskrivning gällande input/output.
typedef std::chrono::high_resolution_clock Clock;<//Skapar en högupplöst klocka.




int main()
{   <//What time it is when the program starts to run.
    auto t1 = Clock::now(); 


    <//Appropriate variables, safely in the global scope.
    string day, time, inOut, temperature, humidity;
    string searchDate;
    string date;
    vector<string>dates;

    double temp = 0;
    double hum = 0;


    <//Opens file if such exists
    ifstream text("tempdata4long.txt");

    <//If file is open
    if (text.is_open()) {

        <//If pointer is not at the end of the file
        while (!text.eof())
        {   


            <//Gets the strings from the file and puts it in the vector
            getline(text, day, ' ');
            <//To make it easier to typecast the string to int later and compare it with a <searchvariable
            date = day.substr(0, 4) + day.substr(5, 2) + day.substr(8, 2);

            <//Puts variable 'date' in vector 
            dates.push_back[date];
            getline(text, time, ',');
            dates.push_back[time];
            getline(text, inOut, ',');
            dates.push_back[inOut];
            getline(text, temperature, ',');
            dates.push_back[temperature];
            getline(text, humidity);
            dates.push_back[humidity];

            <//Typecast till double
            <//temp = stod(dates[3]);
            <//typecast till double
            <//hum = stod(dates[4]);

            <//To test if i managed to put anything in the vector
            for (int i = 0; i < dates.size(); i++)
            {
                cout << dates[i];
            }

        }



    }

    <//What time it is 
    auto t2 = Clock::now();

    <//Prints how many milliseconds the programme took
    cout << "Programmet tog " << chrono::duration_cast <chrono::milliseconds> (t2 - t1).count();

    <//A way to end the programme or find out if anything else needs to be done.
    string y;
    getline(cin, y);

    return 0;



}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • 1
    `while (!text.eof())` -- [Read this](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons) – PaulMcKenzie Nov 22 '19 at 13:45
  • "put in a vector/array" *of what*? "a struct that holds date, time, location, temp, humidity" is a type that you need to define. – Caleth Nov 22 '19 at 13:57
  • At a guess, this is an assignment where you should define a `struct Thing { ... };` and `>>` for it (a function that starts `std::istream& operator >>(std::istream& is, Thing & thing)`). Then you can populate a `std::vector things;` – Caleth Nov 22 '19 at 14:09
  • 1
    Whenever you write a comment that explains what the next lines of code do, consider writing a new method instead with a descriptive name and move those lines into that method. That tends to make code much more readable and understandable. – Max Vollmer Nov 22 '19 at 14:20
  • 1
    Also *"Appropriate variables, safely in the global scope."* nope, nope, nope, nope. This is the exact opposite of *"safely"*. You want your variables to only exist where and when you need them. Otherwise with increasing code complexity, it will become more and more difficult to figure out which variable does what and when; when, where and how you can (re)use them and for what; always needing to check how they were used before and how they will be used later. Anything "global" or existing in scopes beyond the absolute necessary minimum is just a risk for introducing hard to debug bugs. – Max Vollmer Nov 22 '19 at 14:24
  • And last but not least, please read [Why is “using namespace std;” considered bad practice?](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice) – Max Vollmer Nov 22 '19 at 14:26
  • Oh and [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173) – Max Vollmer Nov 22 '19 at 14:27
  • @AlgirdasPreidžius, yes, it was the brackets, thanks for seeing that. That change gave me the control of the data that I intended. If you have any recomendations about a "good c++" - book, please let me know. –  Nov 22 '19 at 16:29
  • @Caleth thanks for helping a beginner at stackoverflow :) . I tried doing what you describe before I ended up at this point but have now changed my strategy since this is an assignement with a duedate. So the strategy now is to make everything work in one file and then improve it with breaking out functions and setting variables in local scopes. This assignement is supposed to lead to further learning and it definitely has. –  Nov 22 '19 at 16:35
  • That sounds like a good strategy to *fail* this assignment. Hint: each `Thing` instance can have it's *own* day, time, inOut, temperature and humidity – Caleth Nov 22 '19 at 16:40
  • @EvySvensson "_If you have any recomendations about a "good c++" - book, please let me know._" I.. Already included a link to the list of C++ books, that was reviewed by SO community.. So I am not sure what more do you need.. – Algirdas Preidžius Nov 22 '19 at 18:18

1 Answers1

0

To solve the problem at hand there is a more or less standard approach. First you analyze WHAT has to be done, then HOW it should be implemented, then do the IMPLEMENTATION and the test the UNIT and at the end test the full SYSTEM.

So, you have to read a CSV file, witht some dates and weather data in it. Then you want to operate on this data, do some calculation etc. The selected compiler language is C++.

Now, how could this to be done? C++ is an object oriented language. You can create objects, consisting of data and member functions that operate on this data. We will define a class "WeatherMeasurement" and overwrite the inserter and extractor operator. Because the class and only the class should know how this works. Having done that, input and output becomes easy.

Example:

WeatherMeasurement wm{};

// Do something
// . . .

//Output
std::cout << wm;

The extractor, and that is the core of the question is a little bit more tricky. How can this be done?

In the extractor we will first read a complete line from an std::istream using the function std::getline. After having the line, we see a std::string containing "data-fields", delimited by a comma. The std::string needs to be split up and the "data-fields"-contents shall be stored.

The process of splitting up strings is also called tokenizing. The "data-fields"-content is also called "token". C++ has a standard function for this purpose: std::sregex_token_iterator.

And because we have something that has been designed for such purpose, we should use it.

This thing is an iterator. For iterating over a string, hence sregex. The begin part defines, on what range of input we shall operate, then there is a std::regex for what should be matched / or what should not be matched in the input string. The type of matching strategy is given with last parameter.

1 --> give me the stuff that I defined in the regex and
-1 --> give me that what is NOT matched based on the regex.

We can use this iterator for storing the tokens in a std::vector. The std::vector has a range constructor, which takes 2 iterators a parameter, and copies the data between the first iterator and 2nd iterator to the std::vector.

The statement

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

defines a variable "tokens" of type std::vector<std::string>, splits up the std::string and puts the tokens into the std::vector. After having the data in the std::vector, we will copy it to the data members of our class.

Very simple.

Next step. We want to read from a file. The file conatins also some kind of same data. The same data are rows.

And as for above, we can iterate over similar data. If it is the file input or whatever. For this purpose C++ has the std::istream_iterator. This is a template and as a template parameter it gets the type of data that it should read and, as a constructor parameter, it gets a reference to an input stream. It doesnt't matter, if the input stream is a std::cin, or a std::ifstream or a std::istringstream. The behaviour is identical for all kinds of streams.

And since we do not have files an SO, I use (in the below example) a std::istringstream to store the input csv file. But of course you can open a file, by defining a std::ifstream csvFile(filename). No problem.

We can now read the complete csv-file and split it into tokens and get all data, by simply defining a new varible and use again the range constructor.

std::vector weatherData(std::istream_iterator<WeatherMeasurement>(csvFile), {});

This very simple one-liner will read the complete csv-file and do all the expected work.

Please note: I am using C++17 and can define the std::vector without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").

Additionally, you can see that I do not use the "end()"-iterator explicitely.

This iterator will be constructed from the empty brace-enclosed initializer list with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.

I added some functions in main to show you, how to operate on the data. All this function should also be put into a class, but I think, I already answered you basic question.

Please see:

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

const std::regex csvSeparator{ "," };


struct  WeatherMeasurement {
    // Data
    std::string date{};
    std::string time{};
    std::string inout{};
    double temperature{};
    double humidity{};

    // Overwrite inserter operator
    friend std::ostream& operator << (std::ostream& os, const WeatherMeasurement& wm) {
        return os << wm.date << " " << wm.time << " " << wm.inout << " " << wm.temperature << " " << wm.humidity;
    }

    // Overwrite extractor operator to read csv data
    friend std::istream& operator >> (std::istream& is, WeatherMeasurement& wm) {

        // We will read one line from the stream
        std::string textLine{};

        // Read the line and check, if it worked
        if (std::getline(is, textLine)) {

            // Split the line into tokens
            std::vector tokens(std::sregex_token_iterator(textLine.begin(), textLine.end(), csvSeparator, -1), {});

            // We expect 5 tokens for our private data
            if (5 == tokens.size()) {
                // Ok, data is available. Now put it into our private data members
                wm.date = tokens[0];
                wm.time = tokens[1];
                wm.inout = tokens[2];
                wm.temperature = std::stod(tokens[3]);
                wm.humidity = std::stod(tokens[4]);
            }
        }
        return is;
    }
};

std::istringstream csvFile{ R"(2019/12/01,01:23:34,inout1,32,50
2019/12/01,01:23:35,inout2,33,51
2019/12/02,02:23:35,inout3,29,48
2019/12/03,03:23:35,inout4,28,47
2019/12/04,04:23:35,inout5,26,51
)" };


int main() {
    // Read the complete csv file and get all weater measurements
    std::vector weatherData(std::istream_iterator<WeatherMeasurement>(csvFile), {});

    // Get the min and max temperature
    const auto [min1, max1] = std::minmax_element(weatherData.begin(), weatherData.end(), 
        [](const WeatherMeasurement& wm1, const WeatherMeasurement& wm2) { return wm1.temperature < wm2.temperature; });
    std::cout << "Min/Max Temperature: " << min1->temperature << " / " << max1->temperature << "\n";

    // Get the min and max humidity
    auto [min2, max2] = std::minmax_element(weatherData.begin(), weatherData.end(),
            [](const WeatherMeasurement& wm1, const WeatherMeasurement& wm2) { return wm1.humidity < wm2.humidity; });
    std::cout << "Min/Max Humidity: " << min2->humidity << " / " << max2->humidity << "\n";

    // Average Temparature
    std::cout << "\nAverage Temperature : " 
        << static_cast<double>(std::accumulate(weatherData.begin(), weatherData.end(), 0,
            [](double init, WeatherMeasurement& wm1) { return init + wm1.temperature; })) / weatherData.size() << "\n\n";

    // Sort by temperature
    std::sort(weatherData.begin(), weatherData.end(),
        [](const WeatherMeasurement & wm1, const WeatherMeasurement & wm2) { return wm1.temperature < wm2.temperature; });
    std::copy(weatherData.begin(), weatherData.end(), std::ostream_iterator<WeatherMeasurement>(std::cout, "\n"));

    // Average Humidity
    std::cout << "\n\nAverage Humidity : "
        << static_cast<double>(std::accumulate(weatherData.begin(), weatherData.end(), 0,
            [](double init, WeatherMeasurement & wm1) { return init + wm1.humidity; })) / weatherData.size() << "\n\n";

    // Sort by humidity
    std::sort(weatherData.begin(), weatherData.end(),
        [](const WeatherMeasurement & wm1, const WeatherMeasurement & wm2) { return wm1.humidity < wm2.humidity; });
    std::copy(weatherData.begin(), weatherData.end(), std::ostream_iterator<WeatherMeasurement>(std::cout, "\n"));

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