Trouble reading csv file into vector of struct

So I have a csv file with stock information and I'm trying to read the file into a vector of a struct I have created with members representing the different data in the file. I get a syntax error for getline saying that Data has no member 'date', ''open', etc. What am I doing wrong?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>

using namespace std;

struct Stock
{
	string date;
	string open;
	string high;
	string low;
	string close;
	string adjClose;
	string volume;
};

int main()
{
	fstream myFile;
	ifstream myFile("AAPL.csv", ios::in);
	if (!myFile.is_open())
	{
		cout << "File failed to open." << endl;
	}
        vector<Stock> Data;
	while (myFile.good())
	{
		getline(myFile, Data.date, ',');
		getline(myFile, Data.open, ',');
		getline(myFile, Data.high, ',');
		getline(myFile, Data.low, ',');
		getline(myFile, Data.close, ',');
		getline(myFile, Data.adjClose, ',');
		getline(myFile, Data.volume, ',');
	}
	system("pause");
	return 0;
}
Data is a vector, not a struct. I think you mean to do something like this:
1
2
3
4
5
6
7
8
9
10
11
12
  vector<Stock> Data;
  Stock temp;
  while (getline(myFile, temp.date, ','))
  {
    getline(myFile, temp.open, ',');
    getline(myFile, temp.high, ',');
    getline(myFile, temp.low, ',');
    getline(myFile, temp.close, ',');
    getline(myFile, temp.adjClose, ',');
    getline(myFile, temp.volume, ',');
    Data.push_back(temp);
  }


EDIT:
Also, your lines 23 and 24 declare the same identifier of different data types. Your compiler will complain about this.

I personally changed your good() in the while (because I have had issues with it in the past), but it seems to work if you have what you originally had as well.
Last edited on
Hello AshtoKream,

lines 23 and 24 are the same thing. I would remove line 23.

On line 24 you do not need the , ios::in because the "ifstream" already knows that it is for input. You only need , ios::in when it is set up as a "fstream".

Your if statement would work better as:
1
2
3
4
5
if (!myFile)
{
	cout << "File failed to open." << endl;
        return 1;
}

"!myFile" is all you need and the return will exit the program because there is no reason to continue with the program until it is fixed and the if statement is bypassed.

The while condition would work better as: while (getline(myFile, Data.date, ',')). This way when you try to read past end of file the stream fails and the while condition becomes false and thus the while loop fails.

On line 38 remove the , ','. You should just need to read until you find the new line. Otherwise you will read the rest of that line and the beginning of the next line until it finds a comma.

Your program uses an input file. Post the file or at least a fair sample so everyone is working with the same information.

Hope that helps,

Andy
Hello AshtoKream,

Sorry I was so focused on other parts I missed what fiji885 has pointed out, but the rest still applies.

Andy
Hello AshtoKream,

After running the program with the above changes I came to the conclusion that after date everything else would work better as a number and tried this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct Stock
{
	std::string date;
	double open;
	double high;
	double low;
	double close;
	double adjClose;
	double volume;
};

std::vector<Stock> Data;
Stock temp;
char junk{};// <--- Could hafe called it comma.

while (std::getline(myFile, temp.date, ','))
{
	myFile >> temp.open; myFile >> junk;

	myFile >> temp.high; myFile >> junk;
	myFile >> temp.low; myFile >> junk;
	myFile >> temp.close; myFile >> junk;
	myFile >> temp.adjClose; myFile >> junk;
	myFile >> temp.volume;

	myFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.
		
	Data.emplace_back(temp);
}

Not sure what you have in mind for the rest of the program, but this way you will not have to change the strings into numbers before you use them. The "junk" variable is there to eat the commas in the line. And myFile >> is formatted input that will read a number until it finds something that is not a number, i.e. the comma. "junk" reads the comma leaving the file pointer ready to read the next number.

The ignore statement clears the input buffer leaving it clear for the next "getline" in the while condition.

Hope that helps,

Andy
Thanks so much Andy and fiji885, that really helped me understand what I needed to do. So what I'm supposed to do with the data is to output the number of days recorded in the file, and then ask the user to input how many trading days they would like to look at. Then output the data for the amount of days the user asked for, with the greatest percent change between "open" and "close" in order of highest to low. So I don't think I even need to use any of the data except for date, open, and close. A sample of the file would look like this.

Date Open High Low Close Adj Close Volume

1/8/2019,149.559998,151.820007,148.520004,150.75,150.75,41025300
1/9/2019,151.289993,154.529999,149.630005,153.31,153.31,45099100
1/10/2019,152.5,153.970001,150.860001,153.800003,153.800003,35780700

For x amount of days.

The output would probably need to look like:

Date Open Close Percent Change
xdate xopen xclose xpercent change


And so on and so forth for the amount of days entered.
Should I create a struct member for percent change that way I can store it with each row?


Hello AshtoKream,

To answer your final question. Yes, that is what I did.

What I did was to create a struct I called "Date".

The structs look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Date
{
	size_t s_month{};
	size_t s_day{};
	size_t s_year;
};

struct Stock
{
	std::string date;
	Date sDate;
	double open{};
	double high{};
	double low{};
	double close{};
	double adjClose{};
	double volume{};
	double percentChange{};
};


In "main" I added the variable std::string tempDate then changed the while loop:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

	while (std::getline(myFile, tempDate, ','))
{
	std::istringstream iss(tempDate);

	iss >> temp.sDate.s_month >> junk >> temp.sDate.s_day >> junk >> temp.sDate.s_year;

	temp.sDate.s_month < 10 ? temp.date = std::to_string(0) + std::to_string(temp.sDate.s_month) : temp.date = std::to_string(temp.sDate.s_month);

	temp.date += '/';

	temp.sDate.s_day < 10 ? temp.date += std::to_string(0) + std::to_string(temp.sDate.s_day) : temp.date += std::to_string(temp.sDate.s_day);

	temp.date += '/';

	temp.date += std::to_string(temp.sDate.s_year);

In the end it makes the output look nicer. One of my little pet peeves.

After reading the rest of the line I used an if/else statement to figure the percent change. I will admit I am not sure if this is correct or even the best way to accomplish this task, but it does seem to work, if I understand the formula correctly.
1
2
3
4
5
6

if (temp.close - temp.open > 0)
	temp.percentChange = (((temp.close - temp.open) / temp.open) * 100);
else
	temp.percentChange = -((temp.open - temp.close) / temp.open) * 100;

After a thought and testing I found that line 3 is all you should need. I started with the if/else statements because the formulas for gain and loss are different. So far the formula for gain will word both ways.

I created this function to print a report based on the range of dates. I do not know what the full input file looks like, but you might want a variable for month.

This is something you can use as an example to work from:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

void Report(std::vector<Stock> data, size_t sRange, size_t eRange)
{
	std::cout << std::fixed << std::showpoint << std::setprecision(2);

	std::cout << std::setw(28) << ' ' << "Percent" << std::endl;

	std::cout << "   Date     Open    Close   Change" << std::endl;

	for (auto& lc : data)
	{  //  Date Open Close Percent Change
		if (lc.sDate.s_day >= sRange && lc.sDate.s_day <= eRange)
			std::cout << std::setw(10) <<lc.date
			<< std::setprecision(2) << ' ' << lc.open << "  "
			<< lc.close << "   " << std::setw(5) << std::setprecision(2) << lc.percentChange << " %" << std::endl;
	}
}

Most of the time I just need the setprecision of line 4.

I put the "setprecision"s on line 14 and 15 because I once changed line 15 to 4 and needed line 14 to set it back to 2.If everything will contain the same number of decimal places then all you need is line 4 and you can loose the "setprecision"s from lines 14 and 15.

Just remember the "setw" on line 15 is for two decimal places. If you change the "setprecision" you will need to increase the "setw' by the same amount.

Hope that helps,

Andy
Goodness thank you so much man, you're teaching me so much lol. So when I call to report what is my first argument? I don't know what I'd be passing to the function. And for sRange and eRange I assume I'm putting 0 or 1 for sRange? Then the user input for how many days for eRange?

Again thanks so much man.
Hello AshtoKream,

The order of the arguments makes no difference. What you pass is what you need. When I first started with the function I knew I needed the vector. The other two variables I added later. Looking back I should have called them "sDate" and "eDate", but either word works. The important part is that the prototype and function definition match. Then in the function call you use the variable names in the same order.

A lot of the time I will copy the function definition and use it for the prototype. The other times I will write the prototype and use it for the function definition.

What you pass to a function will depend on what the function does and what you will need. When I started writing the function I know I needed the vector to work with. The "range" variables came next and at a later time when I figured out I needed. Just as I was later thinking that the number for the month may be useful in the function. "month" came as an after thought because the input file I have to use only has information for one month, but could contain information for more than one month. At some point you may want to deal with information for three months or six months or just keep it to one month at a time.

My idea for the "sRange" and "eRange" variables is that back in "main" you ask the user for the start and end dates for the "range" variables. Then pass the variables to the function. This way start and end could either be the same number or different numbers for a range to be used by the if statement.

Think about it:
And for sRange and eRange I assume I'm putting 0 or 1 for sRange?
What month starts with "0"?

For now I hard coded the the numbers in the function, but that is for testing the program and that I did not want to take the time to write the input parts for there numbers. To do it right the hard coded numbers should be replaced with variables.

I do not know what your full input file looks like, but for what I have I need to make some adjustments and additions to really test the program.

Hope that helps,

Andy
Topic archived. No new replies allowed.