The brief
A small town library has a front desk with one tired computer and a list of jobs that staff currently do by hand. The head librarian has asked you for three small tools. She does not want a polished app. She wants designs she can read and programs she can trust, so that her volunteers can check the rules against what the programs do.
For each of the three tools you will go through the whole method from this module:
- Restate the problem and list the inputs and outputs.
- Draw a flowchart (in a
textblock, using the symbols from Lesson 2). - Write pseudocode (using the conventions from Lesson 3).
- Translate it into a Python program that reads from the Input box and prints to the screen.
- Trace one example by hand with a trace table (Lesson 4), and check that the program prints the same values.
- Choose test inputs on purpose and record the expected and actual output in a test table.
The three tools are the late-fee calculator, the second-highest-rating finder and the number guessing referee. They use decisions, loops, early exits and binary search, which is everything in this module.
Tool A: the late-fee calculator
Specification
A borrower returns a book d days late. The fee is worked out day by day, using these rules:
- Days 1 to 3 are a grace period and cost nothing.
- Days 4 to 14 cost 2 per day.
- Day 15 and every later day cost 5 per day.
- The fee never goes above 100.
So a book that is 4 days late costs 2 (only day 4 is charged). A book that is 14 days late costs 11 charged days at 2 each, which is 22. A book that is 15 days late costs 22 plus 5, which is 27.
Input: one line holding a whole number d with d >= 0.
Output: one line holding the fee, as a whole number.
Examples
| Input | Output | Why |
|---|---|---|
0 | 0 | Returned on time |
3 | 0 | Last day of the grace period |
4 | 2 | One charged day |
14 | 22 | Eleven days at 2 |
15 | 27 | 22 plus one day at 5 |
30 | 100 | 22 + 16 x 5 = 102, capped at 100 |
Type the input into the Input box of your program. For example, type 15 and the program should print 27.
Tool B: the second-highest rating
Specification
Readers rate a book from 1 to 5 stars, but the librarian wants a general tool for any list of whole numbers. The tool reports the second largest distinct value. "Distinct" means equal values count once, so in 9 9 4 the largest is 9 and the second largest is 4. If the list has fewer than two different values, there is no answer, and the program prints none.
Input: the first line holds n, the number of values (n >= 1). The second line holds the n whole numbers separated by spaces. Values can be negative.
Output: one line with the second largest distinct value, or the word none.
Examples
| Input | Output | Why |
|---|---|---|
3 then 4 9 7 | 7 | 9 is largest, 7 is next |
3 then 9 9 4 | 4 | The repeated 9 counts once |
3 then 5 5 5 | none | Only one distinct value |
1 then 7 | none | Only one value |
3 then -5 -2 -9 | -5 | -2 is largest, -5 is next |
5 then 1 2 2 3 3 | 2 | Distinct values are 1, 2, 3 |
This is the problem that broke in Lesson 4, so use what you learned there.
Tool C: the number guessing referee
Specification
At the library's children's day, a volunteer thinks of a secret number between 1 and N, and a computer plays the guesser. The computer must always guess the middle of the range that is still possible (the lower middle when the range has an even count), and the referee program must print each guess with the verdict, and finally how many guesses were needed.
Input: the first line holds N. The second line holds the secret number s, with 1 <= s <= N.
Output: one line per guess, in the form guess G: too low, guess G: too high or guess G: correct, followed by a final line guesses: K.
"Too low" means the guess is smaller than the secret. Use (low + high) // 2 for the middle.
Examples
Input:
15
11Output:
guess 8: too low
guess 12: too high
guess 10: too low
guess 11: correct
guesses: 4Input 100 then 1 gives six guesses (50, 25, 12, 6, 3, 1) and ends with guesses: 6. Input 1 then 1 gives guess 1: correct and guesses: 1.
Requirements checklist
Copy this list and tick each item when you are sure it is true.
- For each tool, I wrote the inputs and outputs before anything else.
- For each tool, I drew a flowchart in a
textblock with correct symbols, and every diamond has aYesand aNoexit. - For each tool, I wrote pseudocode with
<-for assignment,==for comparison, indented blocks and a contract comment. - Each loop has a stated initialization, condition and update, and I can say why it ends.
- Tool A gives 0 for 0 and 3 days, 2 for 4 days, 22 for 14 days, 27 for 15 days and 100 for 30 days.
- Tool A never prints a fee above 100, including for very large inputs such as
200. - Tool B prints
nonefor5 5 5and for a single value, and treats a repeated maximum correctly. - Tool B works for all-negative numbers and does not start its variables at 0.
- Tool C halves the range every guess and stops the moment the guess is correct.
- Tool C works when
N = 1, and when the secret is1orN. - I traced one example per tool by hand, and the program printed the same values.
- I wrote a test table with at least 5 inputs per tool, including a boundary or edge case, and I ran every one.
- Every program runs in the run box without errors and reads its input with
input().
What to hand in
Write your answer as one document with three sections, one per tool. Each section has these parts in this order: the restated problem, the flowchart, the pseudocode, the Python program, one trace table and a test table. A test table has three columns: input, expected output (worked out by hand before you run anything), and actual output.
Working out the expected output first is the whole point. If you copy the expected output from the program's answer, you are checking the program against itself, and the check proves nothing.
Grading rubric
Use this to mark your own work, or to ask a friend to mark it.
| Level | What it looks like |
|---|---|
| 60% | All three programs run. Tools A and C give the right output for the examples in this brief. Tool B works for ordinary lists such as 4 9 7. Flowcharts and pseudocode exist but may have missing labels, loose indentation or loop bounds that are not stated. One trace table is present. |
| 80% | All programs are correct for every example in the brief, including 9 9 4, 5 5 5 and the all-negative list. Flowcharts use the symbols correctly, and the pseudocode follows the conventions of Lesson 3. Each loop has a clear initialization, condition and update. Every tool has a trace table that matches the program's printed output, and a test table of at least 5 inputs per tool with expected values worked out in advance. |
| 100% | Everything in the 80% level, plus: edge cases are tested on purpose and explained (d = 3 against d = 4, N = 1, a single-value list, a cap hit by a huge input). The student states the loop invariant for Tool B and Tool C in one sentence each. The student explains in two sentences why binary search needs at most about ten guesses for N = 1000, and can name one test input that would have broken a plausible wrong version of each tool, such as s = N for a binary search that uses low < high. |
Step-by-step hints
Try each tool on your own first. Open a hint only when you are stuck, and open one at a time.
Hint A1: how to think about the fee
Count the charged days in two bands. Band one is days 4 to 14, which is min(d, 14) - 3 days when d is at least 4. Band two is days 15 and up, which is d - 14 days when d is at least 15. Multiply by the rates, add them up and apply the cap.
Hint A2: the chain of decisions
Ask the narrowest question first, so each diamond narrows the range. Is d <= 3? If yes, the fee is 0. Otherwise, is d <= 14? If yes, the fee is (d - 3) * 2. Otherwise the fee is 11 * 2 + (d - 14) * 5. Then one more diamond: is the fee above 100? If yes, set it to 100.
Hint A3: the tests
Test the boundaries on both sides: 0, 3, 4, 14, 15, then a value where the cap applies and a value just under the cap. Day 16 costs 32. The first day where the cap applies is day 30, since 22 + 16 x 5 = 102.
Hint B1: remember the bug from Lesson 4
When a new value beats first, move the old first into second before replacing it. And do not start at 0, because the numbers may be negative. Use a marker for "no value yet" (in Python, None).
Hint B2: what about ties?
If x equals first, it must change nothing. It is not a new largest, and it is not a new second largest either, because it is the same value as the largest. So the second branch needs the extra condition x != first.
Hint B3: reading the input
The first line is n and the second line holds the numbers. Use n = int(input()), then A = list(map(int, input().split())). The word split cuts the line at the spaces, and map(int, ...) turns every piece into a whole number. You do not have to use n afterwards, because A already knows its own length.
Hint C1: the two bounds
Keep low and high. Start with low = 1 and high = N. Each pass, set guess = (low + high) // 2, add 1 to count, and print the verdict. If the guess is too low, low = guess + 1. If it is too high, high = guess - 1. If it is correct, stop.
Hint C2: the loop condition
Use while low <= high. Because 1 <= s <= N, the secret is always inside the range, so the loop always ends with a correct guess. With N = 1 the first guess is 1, and it is correct.
Hint C3: tracing
Trace N = 15, s = 11 as in the brief. A row per pass: low, high, guess, verdict. Your trace should show low and high as 1 and 15, then 9 and 15, then 9 and 11, then 11 and 11.
Reference solution
Do not open this until you have your own answer for all three tools. Compare it with yours.
Reference: Tool A (flowchart, pseudocode, program, trace)
Flowchart:
( Start ) -> /read d/
|
< d <= 3 ? >--Yes--> [ fee = 0 ] ------------------+
| No |
< d <= 14 ? >--Yes--> [ fee = (d - 3) * 2 ] --------+
| No |
[ fee = 11 * 2 + (d - 14) * 5 ] --------------------+
|
< fee > 100 ? >--Yes--> [ fee = 100 ]
| No |
+<---------------+
|
/print fee/ -> ( End )Pseudocode:
// Input: d, the number of days late (whole number, d >= 0)
// Output: the fee
FUNCTION lateFee(d)
IF d <= 3 THEN
fee <- 0
ELSE IF d <= 14 THEN
fee <- (d - 3) * 2
ELSE
fee <- 11 * 2 + (d - 14) * 5
END IF
IF fee > 100 THEN
fee <- 100
END IF
RETURN fee
END FUNCTIONProgram:
Type 15 into the Input box. Output:
27Trace for d = 15:
| Step | d | d <= 3? | d <= 14? | fee | fee > 100? |
|---|---|---|---|---|---|
| start | 15 | ||||
| first decision | 15 | No | |||
| second decision | 15 | No | |||
| else branch | 15 | 22 + 5 = 27 | |||
| cap check | 15 | 27 | No |
Output 27. Test table (expected values worked out from the rules by hand):
| Input | Expected | Actual |
|---|---|---|
0 | 0 | 0 |
3 | 0 | 0 |
4 | 2 | 2 |
14 | 22 | 22 |
15 | 27 | 27 |
16 | 32 | 32 |
30 | 100 | 100 |
200 | 100 | 100 |
The decisions are asked in increasing order of days, so each question narrows the range, and the cap is a separate decision that runs after all three branches have merged.
Reference: Tool B (pseudocode, program, trace)
Pseudocode:
// Input: array A with at least one number
// Output: the second largest distinct value, or "none"
FUNCTION secondDistinct(A)
first <- none
second <- none
FOR i FROM 0 TO LENGTH(A) - 1
x <- A[i]
IF first == none OR x > first THEN
second <- first
first <- x
ELSE IF x != first AND (second == none OR x > second) THEN
second <- x
END IF
END FOR
RETURN second
END FUNCTIONProgram:
Type 3 on the first line and 9 9 4 on the second. Output:
4Trace for [9, 9, 4]:
x | First branch? | Second branch? | first | second |
|---|---|---|---|---|
| (start) | none | none | ||
| 9 | first is none: Yes | 9 | none | |
| 9 | 9 > 9: No | x != first is false: No | 9 | none |
| 4 | 4 > 9: No | 4 != 9 and second is none: Yes | 9 | 4 |
The result is 4. In Python, is None is the correct way to test for "no value yet". The loop invariant: after each pass, first is the largest value seen so far and second is the largest value seen so far that is different from first.
Test table:
| Input line 1 | Input line 2 | Expected |
|---|---|---|
3 | 4 9 7 | 7 |
3 | 9 9 4 | 4 |
3 | 5 5 5 | none |
1 | 7 | none |
3 | -5 -2 -9 | -5 |
5 | 1 2 2 3 3 | 2 |
2 | 3 1 | 1 |
The last row has the largest value first, which is the order that hides the old bug in [9, 4, 7], so it is worth keeping. An input in ascending order, such as 1 2 3, should give 2, and it does, because the first branch moves the old first down each time.
Reference: Tool C (pseudocode, program, trace)
Pseudocode:
// Input: N, the top of the range; s, the secret (1 <= s <= N)
// Output: each guess with its verdict, then the number of guesses
PROCEDURE referee(N, s)
low <- 1
high <- N
count <- 0
WHILE low <= high
guess <- (low + high) DIV 2
count <- count + 1
IF guess == s THEN
PRINT "guess", guess, "correct"
PRINT "guesses", count
RETURN
ELSE IF guess < s THEN
PRINT "guess", guess, "too low"
low <- guess + 1
ELSE
PRINT "guess", guess, "too high"
high <- guess - 1
END IF
END WHILE
END PROCEDUREProgram:
Type 15 and then 11. Output:
guess 8: too low
guess 12: too high
guess 10: too low
guess 11: correct
guesses: 4The word break leaves the loop at once, which does the job of the early RETURN in the pseudocode. Trace for N = 15, s = 11:
| Pass | low | high | guess | Verdict | count |
|---|---|---|---|---|---|
| 1 | 1 | 15 | 8 | too low | 1 |
| 2 | 9 | 15 | 12 | too high | 2 |
| 3 | 9 | 11 | 10 | too low | 3 |
| 4 | 11 | 11 | 11 | correct | 4 |
The loop invariant: the secret is always between low and high, inclusive. At the start it is, because low = 1 and high = N. Each verdict removes a guess that is known to be wrong, together with everything on the wrong side of it, so the secret stays inside. Since the range shrinks every pass, the loop must end.
Test table:
N | s | Expected last two lines |
|---|---|---|
15 | 11 | guess 11: correct, guesses: 4 |
100 | 1 | guess 1: correct, guesses: 6 |
1 | 1 | guess 1: correct, guesses: 1 |
1000 | 731 | guess 731: correct, guesses: 10 |
1000 | 500 | guess 500: correct, guesses: 1 |
For N = 1000 and s = 731 the guesses are 500, 750, 625, 687, 718, 734, 726, 730, 732 and 731. The range starts at 1000 values and is halved about ten times. If the loop condition were low < high instead of low <= high, the case N = 1 would never make a guess and the program would print guesses: 0, and so would any secret that is the last candidate left in a range, which is why N = 1 and secrets at the ends of the range are in the test table.
Stretch tasks
If you finished early, try one of these. Each uses only what is in this module.
- Add a fourth tool, a "renewal limit" counter. A reader may renew a book at most 3 times. Read renewal requests, one per line, until a line holds
0. PrintRenewedfor each of the first 3 requests andLimit reachedfor later ones. Draw the sentinel-loop flowchart first. - Change Tool C so that the guesser has only 7 guesses, and print
Out of guessesif it fails. Is 7 enough forN = 100? Prove it with the halving count, then check with a program that tries every secret from 1 to 100. - Change Tool B so that it reports the second largest value without the word "distinct", so
9 9 4gives 9. Which single condition do you remove, and which test row in your table changes?
What this module covered
- An algorithm is a finite, precise list of steps from input to output, and it needs input, output, definiteness, finiteness and effectiveness.
- Every algorithm is built from sequence, selection and iteration, and the same steps solve every valid input.
- A flowchart uses the oval, rectangle, parallelogram, diamond and arrow; a decision has two labelled exits and a loop is an arrow back.
- Pseudocode uses
<-,==,FOR,WHILE,IF,FUNCTIONandRETURN, indented, with loop bounds stated and arrays counted from 0. - A pseudocode
FORincludes its last value, so translate it torange(first, last + 1)and check the first and last counter values. - A trace table has a column per variable and a row per step or pass; do what the code says, and check the loop condition one last time.
- Choose test inputs on purpose: smallest case, ties, negatives, boundaries, ordered and reversed lists.
- Binary search keeps a range that always contains the answer and halves it every guess, so it needs about ten guesses for 1000 values.