PPP2 Chapter 10 Reading example code operator<<

Pages: 123
I mainly need help with the operator<< in the temperature file I/O code in Chapter 10 Section 10.11.2 that we have to define the print_year() function for. And also that function itself (exercise 5).

I typed the code as shown in the book, but when I run it, I get the "bad reading" error that should be printed from one of the overloaded operator<< functions when the temperature reading doesn't end with a closing parenthesis.

Here's the code:
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
// temp_readings.cpp : Defines the entry point for the console application.
// Osman Zakir
// 2 / 7 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 10 Sections 10.1.1 to 10.1.3
// This program takes temperature readings from an input file and prints it to
// output file.  The readings are represented in the form of readings for the
// year, month and hour.  Structs are used to represent Readings, Days, and Months.

#include "../../std_lib_facilities.h"

// not_a_reading == less than absolute zero
const int not_a_reading = -7777;

const int not_a_month = -1;

struct Day
{
	vector<double> hour{ vector<double>(24, not_a_reading) };
};

// a month of temperature readings
struct Month
{
	// [0:11] January is 0
	int month{ not_a_month };

	// [1:31] one vector of readings per day
	vector<Day> day{ 32 };
};

// a year of temperature readings, organized by month
struct Year
{
	// positive == A.D.
	int year;

	// [0:11] January is 0
	vector<Month> month{ 12 };
};

struct Reading
{
	int day;
	int hour;
	double temperature;
};

istream& operator >> (istream &is, Reading &r);
istream& operator >> (istream &is, Month &m);
istream& operator >> (istream &is, Year &y);
string int_to_month(int i);
int month_to_int(const string &s);
bool is_valid(const Reading &r);
void end_of_loop(istream &is, char term, const string &message);
void print_year(ofstream &ofs, const Year &y);

int main()
{
	try
	{
		// open an input file:
		cout << "Please enter input file name\n";
		string iname;
		cin >> iname;
		ifstream ist{ iname };
		if (!ist)
		{
			error("can't open input file ", iname);
		}

		ist.exceptions(ist.exceptions() | ios_base::badbit);

		// open an output file:
		cout << "Please enter output file name\n";
		string oname;
		cin >> oname;
		ofstream ost{ oname };
		if (!ost)
		{
			error("can't open output file ", oname);
		}

		// read an arbitrary number of years:
		vector<Year> ys;
		while (true)
		{
			// get a freshly initialized Year each time around
			Year y;
			if (!(ist >> y))
			{
				break;
			}
			ys.push_back(y);
		}
		cout << "read " << ys.size() << " years of readings\n";

		for (const Year &y : ys)
		{
			print_year(ost, y);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
		keep_window_open();
		return 1;
	}
	catch (const exception &e)
	{
		cerr << "error: " << e.what() << '\n';
		keep_window_open();
		return 2;
	}
	keep_window_open();
	return 0;
}

istream& operator>>(istream &is, Reading &r)
// read a temperature reading from is into r
// format: ( 3 4 9.7 )
// check format, but don’t bother with data validity
{
	char ch1;
	if (is >> ch1 && ch1 != '(') 
	// could it be a Reading?
	{ 
		is.unget();
		is.clear(ios_base::failbit);
		return is;
	}
	char ch2;
	int d;
	int h;
	double t;
	is >> d >> h >> t >> ch2;
	if (!is || ch2 != ')')
	{
		// messed-up reading
		error("bad reading"); 
	}
	r.day = d;
	r.hour = h;
	r.temperature = t;
	return is;
}

istream& operator>>(istream &is, Month &m)
// read a month from is into m
// format: { month feb . . . }
{
	char ch = 0;
	if (is >> ch && ch != '{')
	{
		is.unget();

		// we failed to read a Month
		is.clear(ios_base::failbit);
		return is;
	}

	string month_marker;
	string mm;
	is >> month_marker >> mm;
	if (!is || month_marker != "month")
	{
		error("bad start of month");
	}
	m.month = month_to_int(mm);

	int duplicates = 0;
	int invalids = 0;
	for (Reading r; is >> r;)
	{
		if (is_valid(r))
		{
			if (m.day[r.day].hour[r.hour] != not_a_reading)
			{
				++duplicates;
			}
			m.day[r.day].hour[r.hour] = r.temperature;
		}
		else
		{
			++invalids;
		}
	}
	if (invalids)
	{
		error("invalid readings in month", invalids);
	}
	if (duplicates)
	{
		error("duplicate readings in month", duplicates);
	}
	end_of_loop(is, '}', "bad end of month");
	return is;
}

istream& operator>>(istream &is, Year &y)
// read a year from is into y
// format: { year 1972 . . . }
{
	char ch;
	is >> ch;
	if (ch != '{')
	{
		is.unget();
		is.clear(ios_base::failbit);
		return is;
	}

	string year_marker;
	int yy;
	is >> year_marker >> yy;
	if (!is || year_marker != "year")
	{
		error("bad start of year");
	}
	y.year = yy;

	while (true)
	{
		// get a clean m each time around
		Month m;
		if (!(is >> m))
		{
			break;
		}
		y.month[m.month] = m;
	}

	end_of_loop(is, '}', "bad end of year");
	return is;
}

int month_to_int(const string &s)
// is s the name of a month? If so return its index [0:11] otherwise – 1
{
	vector<string> month_input_tbl =
	{"jan", "feb", "mar", "apr", "may", "jun", 
		"jul", "aug", "sep", "oct", "nov", "dec"};

	for (int i = 0; i < 12; ++i)
	{
		if (month_input_tbl[i] == s)
		{
			return i;
		}
	}
	return -1;
}

string int_to_month(int i)
// months [0:11]
{
	vector<string> month_print_tbl =
	{ "January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December" };

	if (i < 0 || i >= 12)
	{
		error("bad month index");
	}
	return month_print_tbl[i];
}

bool is_valid(const Reading &r)
// a rough test
{
	constexpr int implausible_min = -200;
	constexpr int implausible_max = 200;

	if (r.day < 1 || r.day > 31)
	{
		return false;
	}
	if (r.hour < 0 || r.hour > 23)
	{
		return false;
	}
	if (r.temperature < implausible_min || r.temperature > implausible_max)
	{
		return false;
	}
	return true;
}

void end_of_loop(istream &ist, char term, const string &message)
{
	// use term as terminator and/or separator
	if (ist.fail())
	{
		ist.clear();
		char ch;
		if (ist >> ch && ch == term)
		{
			// all is fine
			return;
		}
		error(message);
	}
}

void print_year(ofstream &ofs, const Year &y)
{
	for (unsigned i = 0; i < y.month.size(); ++i)
	{
		ofs << y.year << ": " << y.month[i].day[i].hour[i] << '\n';
	}
}
Please provide a sample of your input file.

Thanks for the reply.

Here's the input file:

{ year 1992 { month jan ( 1 0 61.5) } {month feb (1 1 64) (2 2 65.2) } }
{year 2000
{ month feb (1 1 68 ) (2 3 66.66 ) ( 1 0 67.2)}
{month dec (15 15 –9.2 ) (15 14 –8.8) (14 0 –2) }
}


I took the example file sample presented in that chapter as potential input for that code. Then I took part of it out because I thought the problem might be there. Still no luck.
Last edited on
Have you run the program with your debugger? Your debugger will be able to tell you exactly where it detects the problem (Hint: It's not in your print_year() function). Once you locate the problem and view the variables you should be able to see what is wrong. Also once you know the location of the problem you can backtrace to see exactly where things go wrong.

EDIT: The problem is going to be difficult to spot because a certain character isn't what it appears to be in your input file. The problem is in December of 2000, and the problem is that those minus signs are not what you think, you need to insure that they are truly minus signs (ASCII 45) and not some other "extended" character with a decimal value of something like 147.

By the way your print_year() function should probably be using a std::ostream parameter and return value instead of a std::ofstream. If you use the ostream parameter then you can "print" to any stream not just an ofstream.



Last edited on
I could already tell that the problem isn't in print_year() since the output of the program shows me that it's coming here:
1
2
3
4
5
if (!is || ch2 != ')')
{
	// messed-up reading
	error("bad reading"); 
}


And throwing that exception because it thinks that I don't have ')' somewhere in the readings to the reading set.

This is the output (from running it in VS):

Please enter input file name
temps_input.txt
Please enter output file name
temps_output.txt
error: bad reading
Please enter a character to exit
k
Press any key to continue . . .


If there's a problem in the code, it'd be because I didn't copy it from the book correctly. Because the code I'm using is from the book.
If there's a problem in the code, it'd be because I didn't copy it from the book correctly. Because the code I'm using is from the book.

It's not a problem with the code, it's a problem with your input file, please read the EDIT in my previous post.

And throwing that exception because it thinks that I don't have ')' somewhere in the readings to the reading set.

Not necessarily a missing ')' since the first part of that if() statement is checking the stream state. If the stream state is bad it won't even bother with the rest of the if() statement.

I could already tell that the problem isn't in print_year()

Then why is your Original Post talking about output operators and your print_year() function?
I mainly need help with the operator<< in the temperature file I/O code in Chapter 10 Section 10.11.2 that we have to define the print_year() function for.

The operator<< is an output operator, and you have no operator<< defined, the only output "function" is your print_year(). You really need to be more precise with your problem descriptions.

LASTLY AND MOST IMPORTANT: You also failed to answer my first question! Did you or did you not run your program with your debugger? The debugger is a very valuable tool that you need to learn to use, it makes finding problems much easier. Debugging and problem solving is probably the biggest part of programming and it is crucial that you learn to use all the tools available.

Last edited on
I mentioned that because the exercise I was trying to is about that and I also wanted to see if there were any problems with the way I'd defined the function (unrelated to the problem with the "bad reading" error, but yeah). Yeah, I admit I wasn't clear enough with the fact that I knew they were two unrelated problems. Sorry about that.

If the problem is with the '-' characters for December of 2000, though, how do you suggest I fix that? Do I put in code that checks if it's a negative number or a hyphen? Or do I make another input file?
If the problem is with the '-' characters for December of 2000, though, how do you suggest I fix that? Do I put in code that checks if it's a negative number or a hyphen? Or do I make another input file?

Did you re-read my EDIT as I suggested? I told you where the problem is and basically what the problem is.

To be more direct the problem is that those '–' characters are not the minus sign, they're something else with a decimal value of 149 instead of the decimal value of 45.

Do I put in code that checks if it's a negative number or a hyphen?

Look closely at these two characters, the first is what is in your file, the second is a minus sign: '–' '-' can you see any difference?
Since the character is not a minus sign (or hyphen) the stream fails because the character is not a valid character for a numeric type. All you need to do is retype those characters with the proper minus sign, you'll need to use an editor that doesn't add any special characters to your file. I'm not a Windows user and am not sure what you're using to edit that file, I can't tell you what program you can use to accomplish this.



I'm using notepad for the file. I'll change it to minus sign manually, then. I'll get back to you here about the print_year() function after this.

Edit: Alright, the input operator problem is fixed. I need help with that printing function now. I've fixed the ostream vs. ofstream issue you mentioned before already, by the way.
Last edited on
I need help with that printing function now.

And what do you need help with?

The print_year() function.
1
2
3
4
5
6
7
void print_year(ostream &os, const Year &y)
{
	for (unsigned i = 0; i < y.month.size(); ++i)
	{
		os << y.year << ": " << y.month[i].day[i].hour[i] << '\n';
	}
}


This is what the output file looks like right now:

1992: -7777
1992: 64
1992: -7777
1992: -7777
1992: -7777
1992: -7777
1992: -7777
1992: -7777
1992: -7777
1992: -7777
1992: -7777
1992: -7777
2000: -7777
2000: 68
2000: -7777
2000: -7777
2000: -7777
2000: -7777
2000: -7777
2000: -7777
2000: -7777
2000: -7777
2000: -7777
2000: -7777


-7777 is supposed to be the default temperature and it's way too low to be realistic. Guess it wasn't read into. This is a problem.

Now I have an unrelated question to ask as well: I need to know Differential Calculus, Linear Algebra and apparently one other math discipline (I forgot what that one is, though) for GUI programming to sink in well, right? If that's true, should I try to learn those first before starting on Chapter 12? I need to get done with this book ASAP without glossing over anything or without being unable to understand what's going on because I'm already at an age where I should be getting a job (I'm 26 and am turning 27 on April 17th), but I want to complete my studies first.

On that end, I'm also taking Introduction to Computer Science on edx.org. I signed up with an organization when I registered for that course that helps you get a job with just you yourself and your skills being looked at rather than your experience being considered. But I'm at a position where I want to find an online job and that might not be possible. I'm close to finishing that course, though, as I just need to resubmit all of the C stuff and then I can start on the Python part of the course (Web Programming). If it's not a bad idea, I also want to read the follow-up book to PPP2.

Anyway, I want to ask you: do you think this is feasible for me? I do want to at least make a personal website for myself with a portfolio page and put it online. Hopefully I can find a cheap or free webhost that supports Python and/or C++ (I'm using Windows, though, so if I do use C++ I guess I'll need a way to convert my .exe files into the type that a server would accept). I'd like to know what code I should put into my online portfolio when I've made it (if any).
Last edited on
-7777 is supposed to be the default temperature and it's way too low to be realistic. Guess it wasn't read into. This is a problem.

Why is it a problem? Your data file doesn't have all the hours of every day of every month filled in so there should be quite a few entries like that. Perhaps you should skip some of these entries. By the way the file may also have bad data in order to test the file reading functions.

I need to know Differential Calculus, Linear Algebra and apparently one other math discipline (I forgot what that one is, though) for GUI programming to sink in well, right?

While having decent math knowledge isn't a bad idea, I doubt you'll need all that math for simple GUIs.

Hopefully I can find a cheap or free webhost that supports Python and/or C++ (I'm using Windows, though, so if I do use C++ I guess I'll need a way to convert my .exe files into the type that a server would accept).

Normally you wouldn't be putting executable files onto the server, you'd post samples of your source code. Having executables on the server doesn't really show your proficiency at programming.

I signed up with an organization when I registered for that course that helps you get a job with just you yourself and your skills being looked at rather than your experience being considered.


Yea, right. Any potential employer is going to look at the whole package, not just bits and pieces.

But I'm at a position where I want to find an online job and that might not be possible.

You probably would be better off asking these questions in a new topic, probably in the lounge.


I was talking about implementing the backend in Python and/or C++ and trying to find a webhost that supports that. I heard that servers don't support .exe files on the backend, though, and that developers have to do something to the .exe file to make the server accept it (I don't know what that is).

The part about what programs to show on the portfolio was separate from that. I'm not sure what programs to show on there out of the ones I've written so far, if any.

Anyways. So you're saying my print_year() function and input operators are just fine and that the issue is, again, with the input file (done on purpose for testing)? Did I understand that right?

I could test it to see if it's -7777, but I'd have to get at the temperatures from the years.
Anyways. So you're saying my print_year() function and input operators are just fine and that the issue is, again, with the input file (done on purpose for testing)? Did I understand that right?

Do you realize that this "project" was deliberately complicated in several ways as a learning experience?

The sample data file seems to have data that tests the read routines so the data contains years without an months, years with a couple of months, but no days, etc.. You may also have duplicate data and possible invalid data as well. Also, IMO, the layout of the data structures is fairly complicated for the same reasons.

I suggest you re-read section 10.11.1 carefully since this section thoroughly explains the data layout that you need to print.

I could test it to see if it's -7777, but I'd have to get at the temperatures from the years.

Well you'll need to test, for each year's data, to see if the month is "empty" of data and if it is you'll probably want to skip that month. Then for months that have data you'll probably want to check if a day has any data if if it doesn't skip that day. Then for days with data you'll probably want to skip the hours that don't have any readings and you'll probably want to keep track of which hour the temperature was recorded for your printout.

By the way your output data file, as last reported, doesn't seem to have all of the information it should.


The print_year() function seems to only be taking an ostream object and the Year object, nothing else. So I have to find a way to print all of the information pertaining to a given year of readings?

If my output file doesn't have all the data, that means my print_year() function implementation is missing something, right? I'll try to see what it is.

Edit: I've got the function like this now:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void print_year(ostream &os, const Year &y)
{
	for (size_t i = 0; i < y.month.size(); ++i)
	{
		if (y.year == 0 || y.month.empty() && y.month[i].day.empty())
		{
			// do nothing
		}
		else if (y.month.empty() || y.month[i].day.empty())
		{
			// do nothing
		}
		else
		{
			os << "Year " << y.year << ": " << "Month: " << y.month[i].month <<
				"Temperature: " << y.month[i].day[i].hour[i] << '\n';
		}
	}
}


Do I have to add code in the operator>> functions to make sure that I have valid readings? Because right now I can't seem to get it to work.
Last edited on
The print_year() function seems to only be taking an ostream object and the Year object, nothing else.

That sounds correct.

So I have to find a way to print all of the information pertaining to a given year of readings?

Well a "year" contains the "months" that contain the "days" that contains the "hours" so it looks like you should have all of the information you need. The trick is extracting the data in a "sane" "useful" way, which is the point of this exercise.

Do I have to add code in the operator>> functions to make sure that I have valid readings?

I think the validations are already in your extraction operators, nothing else should be needed there.

Because right now I can't seem to get it to work.

It doesn't look like you're properly iterating over the months, days, and hours. All I see is an attempt to iterate through the years.

So what am I missing from the above code? Would you give me a hint, please? If that would be a good idea (if it won't help me learn, then just don't give one). I've already got this in there, at least:
1
2
3
4
5
6
7
8
9
10
11
12
13
if (y.year == 0 || y.month.empty() && y.month[i].day.empty())
{
	// do nothing
}
else if (y.month.empty() || y.month[i].day.empty())
{
	// do nothing
}
else
{
	os << "Year " << y.year << ": " << "Month: " << y.month[i].month <<
		"Temperature: " << y.month[i].day[i].hour[i] << '\n';
}


Am I still not iterating over it correctly?

In the "do nothing" parts, I'm trying to not print anything if a certain year or month has nothing in it. I guess I'm doing it wrong if it isn't helping.
Am I still not iterating over it correctly?

In the code above you're not iterating over anything. In your previous post you were iterating over only the months, you need to also iterate over the days and hours.

You probably should start with some Pseudo code to hep make sense of what you need to do.
1
2
3
4
5
6
7
for each year
   if(year has a valid month)
      print the month
      if(month has a valid day)
         print the day
         if(hour is valid)
            print the hour


Section 10.11.1 should be able to help explain what a makes a valid month, day, and hour.




I at least got it to not print invalid months or temperatures with this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void print_year(ostream &os, const Year &y)
{
	constexpr double abs_zero = -459.67;
	for (size_t i = 0; i < y.month.size(); ++i)
	{
		try
		{
			if (y.month[i].day[i].hour[i] <= abs_zero)
			{
				error("bad temperature reading");
			}
			os << '{' << "Year: " << y.year
				<< " Month: " << int_to_month(y.month[i].month)
				<< " Temperature: " << y.month[i].day[i].hour[i] << "}\n";
		}
		catch (const runtime_error &e)
		{
			cerr << "error: " << e.what() << '\n';
		}
	}
}


This is the output file now:

{Year: 1992 Month: February Temperature: 64}
{Year: 2000 Month: February Temperature: 68}


How do I iterate over the years when the Year struct only has an int variable called year and a Month vector called month? I can only iterate over the months in the year with this, right? Or is that what you mean by "for each year"?
Last edited on
How do I iterate over the years

What? Isn't years a std::vector<Year>? How do you iterate through any container? How are you printing those two years now? Doesn't a year contain 12 months, that contain 'X' number of days, that contain 24 hours?

I at least got it to not print invalid months or temperatures with this:

Why did you use absolute zero? Didn't, as part of the input routines, already throw out temperatures greater than 200 and less than -200? Look at your output:
1
2
3
1992: -7777
1992: 64
1992: -7777

What do the "bad" values all contain? Wouldn't it be better to just test for that value?


By the way you're still not iterating through the days and hours, you're only looking at one hour of one day for each month. [EDIT:] By the way your present code would be accessing one of your vectors out of bounds if a year had a month with a day greater than 24.[/EDIT:]

Do you know what "iterate" means in this content? (iterate == perform repeatedly). Do you know how to perform something repeatedly in C++?
Last edited on
Pages: 123