6. Week 3 Thursday: Review and Practise
≪ 5. Week 3 Tuesday: The Input Buffer | Table of Contents | 7. Week 4 Tuesday: Practise with If Statements ≫We’ve covered a lot of content in the weeks leading up to this point, and I think it’s a good time to look back and get some practise in. We are at a critical moment in the course where we have introduced a lot of techniques for data storage, handling input, and data manipulation, but have not yet introduced techniques for controlling our code with “control flow”. Thus our programs are linear and predictable: every time we run our program, it does exactly the same thing, and it cannot respond to different inputs or different scenarios with different code.
Nonetheless, there are ways to emulate control flow with clever applications of integer division, string indexing, and others. Let’s demonstrate this with an example:
Example 1. Just in Time
Write a C++ program that allows a user to input a time in 24-hour format, then outputs the corresponding time in 12-hour format.
You may not use if statements or for loops. You may only use the string
and iostream
libraries.
SAMPLE RUNS:
Please enter a time in 24-hour format: 4:37
4:37 AM
Please enter a time in 24-hour format: 20:59
8:59 PM
You may assume that the user will always input a valid 24-hour time.
The pseudocode is profoundly straightforward:
- Read the hours and minutes from the user’s input.
- Convert the hours to 12-hour format.
- Print out the converted time, with AM if the inputted hours were less than 12 or PM if the inputted hours were larger than 12.
(Indeed, this code would be far easier to implement if we could use conditional statements and branching control flow, but alas, I am an incarnation of human evil.)
Step 1 can be performed using the input buffer techniques we discussed last Tuesday:
1cout << "Please enter a time in 24-hour format: ";
2
3int hours, minutes;
4char colon;
5cin >> hours >> colon >> minutes;
Step 2 appears straightforward as well — we need only take the remainder of the hours after division by 12 to get it into 12-hour format.
1int new_hours = hours % 12;
I’m putting this in a new variable because I anticipate I’ll need to remember the user’s input to determine if I need to print out “AM” or “PM”. There is a subtle problem — 12:30
will make new_hours
become 0
instead of 12
. In fact, the above code works for all times except for 00:XX
and 12:XX
!
Please spend some time thinking carefully about how you can fix this without using if statements.
Hint
If new_hours
is 0, we want to add 12
to new_hours
. If new_hours
is any other number, we want to add 0
to new_hours
. Is there a way to fill in the ???
in the following code
such that n
holds the value 1
if new_hours
is 0
, and such that n
holds the value 0
if new_hours
is nonzero?
Implementation of step 2
Continuing the hint from before, we can use the fact that new_hours
is guaranteed to hold a value between 0
and 11
, inclusive. Then, 12 - new_hours
will hold a value between 1
and 12
, inclusive, and 12 - new_hours
will be 12 exactly when new_hours
is zero. Thus, integer division by 12 will zero out 12 - new_hours
exactly when new_hours
is not zero, and it will be 1
exactly when new_hours
is zero. This is the missing magic number:
This now correctly converts from 24-hour time to 12-hour time.
The last step we have to implement is the output. We’ve converted the hours properly now, and we just need to print out an AM
or a PM
depending on the time of day. The idea is to store both AM
and PM
in a string, then mathematically define an index
variable that will “point to” the correct part of the string.
1string am_pm = "AMPM";
2int index = ???;
3cout << new_hours << ":" << minutes << " "
4 << am_pm.substr(index, 2) << endl;
Please spend some time and try to fill out the ???
in the above snippet.
Hint
index = 0
if hours
is between 0 and 11, inclusive; we need index = 2
if hours
is between 12 and 23, inclusive. Is there a way for integer division to help us?Implementation of Step 3
hours / 12
(with integer division) will be 0
if hours
is between 0 and 11, and it will be 1
if hours
is between 12 and 23. Thus, we should use the code
1int index = (hours / 12) * 2;
Putting all this code together, we arrive at the following finished product:
Full Solution
1#include <iostream> // needed for cin, cout
2#include <string> // needed for strings
3
4using namespace std;
5
6int main() {
7 // step 1 - procuring input. we need to discard the
8 // extra colon in the middle.
9 cout << "Please enter a time in 24-hour format: ";
10
11 int hours, minutes;
12 char colon;
13 cin >> hours >> colon >> minutes;
14
15 // step 2 - convert hours to 12-hour format.
16 int new_hours = hours % 12;
17 int n = (12 - new_hours) / 12;
18 new_hours += 12 * n;
19
20 // step 3 - produce the output, using substrings to
21 // obtain an "AM" or "PM" based on the hour.
22 string am_pm = "AMPM";
23 int index = (hours / 12) * 2;
24 cout << hours << ":" << minutes << " "
25 << am_pm.substr(index, 2) << endl;
26
27 return 0;
28}
Follow-up Question 2.
Lines 17 and 18 are slightly verbose in the sense that they could be collapsed into a single line. What goes wrong if we instead typed the following?
What’s the fix?
A sceptical student may ask, “What’s the point of learning these techniques if they will inevitably be superseded by if-else control flow?” This is true: in the near future, you will learn how to execute one line of code if some condition is met, and another if not. But there are situations where this is impractical.
Consider, for instance, the problem of converting a single English alphabetic character into its NATO phonetic, Morse code, or braille counterparts. Do you really want to write out 26 different lines of cout
’s, corresponding to the 26 different letters? More broadly, this type of scenario occurs frequently when performing data analysis on categorical data.
We have thus identified an important design pattern. The setup is, given some input that has numerous possible values, produce some output that takes a different value for each input. While it’s natural to for humans to say, “If input has value n
, produce output n
” and repeat this for every possible input-output pair, this is a horrible way to write code. Rather, our above solution says to organise the n
possible outputs in an indexable fashion (i.e. as substrings of a string), then use the input to figure out which “index of output” to use.
We will be able to utilise this design pattern in its fullest potential when we discuss arrays and vectors. We are currently limited to storing string data and accessing substrings; we cannot yet store n
different integer outputs, for instance.
Let’s wrap things up with a few practise problems. In the following, you are only permitted to use the iostream
and string
libraries (though some of these do not even require strings). In addition, you may not use control flow.
Problem 3. Another Time
Write a C++ program that asks a user for a time in 12-hour format, then converts it to a time in 24-hour format. You may assume that the user’s input will be of the form hh:mm AM
or hh:mm PM
.
SAMPLE RUNS
Please enter a time in 12-hour format: 3:54 PM
15:54
Please enter a time in 12-hour format: 12:12 AM
0:12
Hint
char
’s behave just like int
’s.
Problem 4. Getting Warmer...
Write a C++ program that accepts a temperature, in either Farhenheit or Celsius, and converts it to the other. Recall that the conversion formulae are
\[\begin{align*} F = 1.8C + 32 && \textrm{and} && C = \frac{F-32}{1.8}.\end{align*}\]
You may assume the user input will always be of the form ### C
or ### F
.
SAMPLE RUNS
Please enter a temperature: 100 C
212 F
Please enter a temperature: 77 F
25 C
Challenge 5. Days of the Week
This is a more challenging variant of a homework problem.
Write a C++ program that accepts a number between 1 and 7 (inclusive), representing a day of the week, and prints out a statement of the form The [nth] day of the week is [day]!
.
SAMPLE RUNS:
Enter a number between 1 and 7: 5
The fifth day of the week is Thursday!
Enter a number between 1 and 7: 1
The first day of the week is Sunday!
Note: you may choose to start your week on Sunday or Monday (or any other day, I don’t really care). The most important part is getting the output with appropriate spacing. You could load these full outputs into one gigantic string and go from there, but try your best to find a more elegant way around the problem.