Reading Measurements with Single Units
Combined measurements such as '3 feet 7 inches' require one subroutine to read several number and unit pairs from a single line and accumulate them into a total in inches.
When One Measurement per Line Is Not Enough
An earlier version of a measurement program accepted one measurement at a time. A user could type '3 miles' or '1ft', and the program would convert that single measurement into inches, feet, yards, and miles. This design works well until a user types what people naturally say, such as '3 feet 7 inches'.
A single line can now hold several measurements, and each one has its own number and its own unit. The program has to read every one of them from the same line and combine them into one total. This article follows the subroutine that does that job and explains the reasoning behind its structure.
What do you think happens?
What total, in inches, should the subroutine return for the line '3 feet 7 inches'?
Reveal answer
43 inches
Three feet is 36 inches, since each foot is 12 inches. Adding the 7 inches gives 36 + 7 = 43.
Building a Running Total
Set aside illegal inputs for the moment, and the core idea is a running total. The subroutine starts with zero inches. It then repeats one three-part step for each measurement on the line: read the number, read its unit, and add the matching amount to the total. When no measurements remain, it returns the total.
The subroutine recognizes four legal units of measure: inch, foot, yard, and mile. A user may write any of them in singular form, plural form, or abbreviation. Plural spellings such as 'feet' do not follow the simple pattern of adding an s to the singular, so the program must recognize them as their own forms. Lines such as '1 foot' and '3 miles 20 yards 2 feet' are valid because every token after a number is one of the recognized unit forms.
total = 0 inchesNo measurements read yet. Total is 0 inches.
total = 36 inches after '3 feet', then 43 inches after '7 inches'Both pairs read. Total is 43 inches.
Peeking at the Next Character
The draft leaves one question open: how does the program decide that more input remains? A natural approach is to look at the next character and ask whether it is the end-of-line character. The source notes that TextIO.peek() does exactly this kind of look. It examines the next character without consuming it, so the test does not remove anything from the input.
Because the peek leaves the character in place, the reading code that runs next still sees the same number. The peek is a look, not a read. This is why the loop test can be placed before any reading happens without losing a measurement.
Why skipBlanks Runs Before the Test
A peek alone is not enough. If the next character is a space, the end-of-line test tells you nothing useful about whether a measurement follows. The character being examined must be either the end-of-line or a non-blank character that starts a measurement. A space is neither, so it must be dealt with first.
The fix is a skipBlanks step. Skipping blanks guarantees that the next character is either the end-of-line or the start of a measurement, which makes the end-of-line test meaningful. The improved algorithm calls skipBlanks once before the loop, and again at the end of each pass. The first peek must already see a meaningful character, which is why the call before the loop matters.
Place skipBlanks before every end-of-line test, not only at the start. Each pass consumes a number and a unit, and the spaces between tokens then need to be cleared before the next peek can be trusted.
Tracing the Loop Pass by Pass
The same logic can be read as a sequence of decisions. Each time the program reaches the test, it either takes the loop path to process another measurement or leaves the loop to return the total. The loop is the only place where control can leave the repetition, and the exit happens only when the test finds the end of the line.
Tracing '3 feet 7 inches' Through the Loop
Trace the skipBlanks-and-peek loop on the line '3 feet 7 inches' and report the total the subroutine returns.
- 1Start
Set the total to 0 inches. Call
skipBlanksso the first peek sees the number 3 rather than a blank. - 2First pass test
Peek sees the 3. It is not the end of the line, so the loop path is taken.
- 3First pass work
Read the number 3 and its unit feet. Add 3 feet, which is 36 inches, to the total. The total is now 36 inches.
- 4End of pass
Call
skipBlanksto clear the space. The next peek now sees the 7. - 5Second pass
Peek sees the 7, which is not the end of the line. Read 7 and inches, then add 7 inches. The total is now 43 inches.
- 6Exit
After
skipBlanks, the peek finds the end of the line. The decision leaves the loop and returns 43.
The subroutine returns 43 inches.
Notice that the exit check runs at the top of every pass, not just once. The loop does not count how many measurements it expects. It keeps going while the test finds a measurement, and it stops only when the end of the line appears.
Where Bad Input Is Caught
The algorithm so far ignores illegal inputs on purpose. A robust version still needs a plan for them, because a line can contain a misspelled unit, a missing number, or other problems. The source states that if there is any error in the line, the subroutine prints an error message and returns the value -1. This rule is an addition on top of the base logic.
The main program uses a separate convention for ending the session: an empty line. It tests for this before calling the subroutine, because the subroutine assumes the line it receives is not empty. Keeping these two responsibilities apart means the subroutine focuses on converting one line, while the main program controls the flow of the whole session.
Mistakes Beginners Make With the Loop
Common mistake Testing for end-of-line without skipping blanks first
What it looks likeThe line is '3 feet 7 inches', and after reading the 3 and the unit, the next character is a space. Testing it directly says nothing about whether a measurement follows.
Why it failsA space is neither the end-of-line nor the start of a measurement, so the test is not meaningful.
Do this insteadCall skipBlanks before the loop and again at the end of each pass, so the peek always sees a meaningful character.
Common mistake Consuming the character during the test
What it looks likeThe program reads the next character to check whether it is the end of the line, and then the number is gone before the reading step runs.
Why it failsA peek looks at the next character without consuming it. A test that consumes input removes a measurement the reader still needs.
Do this insteadUse peek for the test, and let the reading step consume the number and unit.
Common mistake Assuming a plural unit is just the singular with an s added
What it looks likeThe singular is foot, but the plural is feet.
Why it failsThe plural does not follow the simple pattern, so an s-suffix rule will reject valid input.
Do this insteadRecognize singular, plural, and abbreviated forms of each legal unit as separate accepted spellings.
Common mistake Returning zero for bad input
Why it failsZero is a valid total. A caller cannot distinguish a real zero from an error.
Do this insteadPrint an error message and return -1 when the line contains an error.
Practice Tracing and Error Handling
Trace the skipBlanks-and-peek loop on the line '2 yards 1 foot'. Write down the total after each pass, then state the value the subroutine returns. Next, decide what the subroutine should return for the line '3 feetx', and explain at which point in the loop you would detect the problem.
Hints
- Start at zero and call
skipBlanksbefore the first peek. - A yard and a foot are both legal unit forms, but each one has its own conversion to inches.
- Think about where the unit token is read, and what the subroutine does when that token is not a recognized form.
Key Takeaways
- A line such as '3 feet 7 inches' holds several number and unit pairs, so a reader that handles one measurement per line cannot process it.
- The subroutine starts a running total at zero inches and repeats a read-number, read-unit, add-to-total step until no measurements remain.
- The end-of-line test is only meaningful after
skipBlanks, which runs before the loop and again at the end of each pass, while peek looks at the next character without consuming it. - On bad input the subroutine prints an error message and returns -1, while the main program uses an empty line as its own stop signal so the subroutine never receives one.