Embercode
✕
Flowcharts and PseudocodeLesson 5 of 13

50 min read

Module assignment: Library desk toolkit

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:

  1. Restate the problem and list the inputs and outputs.
  2. Draw a flowchart (in a text block, using the symbols from Lesson 2).
  3. Write pseudocode (using the conventions from Lesson 3).
  4. Translate it into a Python program that reads from the Input box and prints to the screen.
  5. Trace one example by hand with a trace table (Lesson 4), and check that the program prints the same values.
  6. 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

InputOutputWhy
00Returned on time
30Last day of the grace period
42One charged day
1422Eleven days at 2
152722 plus one day at 5
3010022 + 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

InputOutputWhy
3 then 4 9 779 is largest, 7 is next
3 then 9 9 44The repeated 9 counts once
3 then 5 5 5noneOnly one distinct value
1 then 7noneOnly one value
3 then -5 -2 -9-5-2 is largest, -5 is next
5 then 1 2 2 3 32Distinct 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
11

Output:

guess 8: too low
guess 12: too high
guess 10: too low
guess 11: correct
guesses: 4

Input 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 text block with correct symbols, and every diamond has a Yes and a No exit.
  • 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 none for 5 5 5 and 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 is 1 or N.
  • 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.

LevelWhat 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 FUNCTION

Program:

Python · editable

Type 15 into the Input box. Output:

27

Trace for d = 15:

Stepdd <= 3?d <= 14?feefee > 100?
start15
first decision15No
second decision15No
else branch1522 + 5 = 27
cap check1527No

Output 27. Test table (expected values worked out from the rules by hand):

InputExpectedActual
000
300
422
142222
152727
163232
30100100
200100100

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 FUNCTION

Program:

Python · editable

Type 3 on the first line and 9 9 4 on the second. Output:

4

Trace for [9, 9, 4]:

xFirst branch?Second branch?firstsecond
(start)nonenone
9first is none: Yes9none
99 > 9: Nox != first is false: No9none
44 > 9: No4 != 9 and second is none: Yes94

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 1Input line 2Expected
34 9 77
39 9 44
35 5 5none
17none
3-5 -2 -9-5
51 2 2 3 32
23 11

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 PROCEDURE

Program:

Python · editable

Type 15 and then 11. Output:

guess 8: too low
guess 12: too high
guess 10: too low
guess 11: correct
guesses: 4

The word break leaves the loop at once, which does the job of the early RETURN in the pseudocode. Trace for N = 15, s = 11:

PasslowhighguessVerdictcount
11158too low1
291512too high2
391110too low3
4111111correct4

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:

NsExpected last two lines
1511guess 11: correct, guesses: 4
1001guess 1: correct, guesses: 6
11guess 1: correct, guesses: 1
1000731guess 731: correct, guesses: 10
1000500guess 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.

  1. 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. Print Renewed for each of the first 3 requests and Limit reached for later ones. Draw the sentinel-loop flowchart first.
  2. Change Tool C so that the guesser has only 7 guesses, and print Out of guesses if it fails. Is 7 enough for N = 100? Prove it with the halving count, then check with a program that tries every secret from 1 to 100.
  3. Change Tool B so that it reports the second largest value without the word "distinct", so 9 9 4 gives 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, FUNCTION and RETURN, indented, with loop bounds stated and arrays counted from 0.
  • A pseudocode FOR includes its last value, so translate it to range(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.

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Tracing and dry runs