Grade by Grade
A. Programming Concepts and Skills · Arrays and Subprograms

Putting It Together: A Modular List-Processing Program

A1.6 A2.3 A3.1 A3.2 A4.2 A4.5About 75 minutes
Learning goals
  • I can plan a complete program as a set of functions that share data through parameters and return values.
  • I can combine lists, parallel lists, searching, sorting and statistics in one menu-driven program.
  • I can document a program with a header comment, docstrings and meaningful names.
  • I can validate the finished program with a test plan that covers every menu choice and special case.

Words to know

menu-driven program
A program that repeatedly shows a list of choices and runs the matching subprogram until the user chooses to quit.
input validation
Checking that user input is acceptable before using it, and asking again if it is not.
internal documentation
Comments, docstrings and clear names written inside the code to explain it to other programmers.
integration
Joining separately written and tested subprograms into one working program.
specification
A precise description of what a program must do.

Your first complete application

A cross-country ski club wants a small program to record each skier's time for a 5 km race and answer questions about the results: What is the average time? Who was fastest? What did a particular skier get? Can we see the results in order? This lesson builds that program from start to finish, using everything in this unit: lists, parallel lists, linear search, max/min, sorting, built-in and home-made subprograms, parameters, local scope and testing. Then you will build your own.

Step 1: the specification

The program must:

  1. Show a menu repeatedly until the user chooses to quit.
  2. Add a result: a name and a time in minutes (a positive number; ask again if the input is not valid).
  3. Show all results, numbered from 1.
  4. Show statistics: the average time (2 decimals) and the fastest skier. With no results, say so instead of crashing.
  5. Find a skier by name, ignoring upper/lower case; say "Not found." if missing.
  6. Sort the results from fastest to slowest, keeping names and times matched.

Step 2: design

Using top-down design from Modular Design, each requirement becomes one or two functions. Data lives in two parallel lists that main() owns and passes as arguments; there are no global variables.

main()show_menuoutputread_timeinputshow_alloutputaverageprocessingfastestprocessingfindprocessingsort_by_timeprocessinginput / outputprocessing (returns or sorts, no printing)
Structure chart for the ski club tracker. main() calls seven functions. Processing functions (green) take lists as parameters and return results or sort in place; input/output functions (yellow) talk to the user.
show menuread choicechoiceis 6?yesGoodbye!nocall the right functionThe menu loop repeats until the user chooses 6.
Flow of the menu loop in main(). Each pass shows the menu, reads a choice and calls the matching function, until the choice is 6.

Step 3: write and test each function

Write one function at a time and test it on its own before moving on. For example, test find with a name in the middle, the first and last names, a different capitalization and a missing name; test fastest with a tie and a one-element list.

# Club Results Tracker: names and 5 km cross-country ski times (minutes).
def show_menu():
"""Print the menu choices."""
print()
print("1) Add a result 2) Show all 3) Statistics")
print("4) Find a skier 5) Sort by time 6) Quit")
def read_time():
"""Ask until the user enters a positive number; return it as a float."""
while True:
text = input("Time in minutes: ")
if text.replace(".", "", 1).isdigit() and float(text) > 0:
return float(text)
print("Please enter a positive number, e.g. 21.5")
def find(names, target):
"""Return the index of target in names (ignoring case), or -1."""
for i in range(len(names)):
if names[i].lower() == target.lower():
return i
return -1
def average(values):
"""Return the mean of a non-empty list."""
total = 0
for v in values:
total += v
return total / len(values)
def fastest(values):
"""Return the index of the smallest value in a non-empty list."""
best = 0
for i in range(1, len(values)):
if values[i] < values[best]:
best = i
return best
def sort_by_time(names, times):
"""Selection sort on times, keeping names parallel (modifies both)."""
for start in range(len(times) - 1):
small = start
for i in range(start + 1, len(times)):
if times[i] < times[small]:
small = i
times[start], times[small] = times[small], times[start]
names[start], names[small] = names[small], names[start]
def show_all(names, times):
"""Print every skier and time, numbered from 1."""
for i in range(len(names)):
print(str(i + 1) + ".", names[i], times[i])

Part 1: the helper functions. Notice the header comment, the docstrings, and that only show_menu, read_time and show_all use input() or print().

Two details worth noticing:

  • read_time validates input with string methods: text.replace(".", "", 1) removes one decimal point, and isdigit() then checks that only digits are left. So "21.5" is accepted but "fast", "-3" and "2.1.1" are not.
  • sort_by_time is the selection sort from Sorting Algorithms, with a second swap that keeps the parallel names list in step.
def main():
names = []
times = []
choice = ""
while choice != "6":
show_menu()
choice = input("Choice: ")
if choice == "1":
names.append(input("Name: ").strip())
times.append(read_time())
elif choice == "2":
show_all(names, times)
elif choice == "3":
if len(times) == 0:
print("No results yet.")
else:
print("Average:", round(average(times), 2))
best = fastest(times)
print("Fastest:", names[best], times[best])
elif choice == "4":
i = find(names, input("Name to find: "))
if i == -1:
print("Not found.")
else:
print(names[i], "skied", times[i], "minutes")
elif choice == "5":
sort_by_time(names, times)
show_all(names, times)
elif choice != "6":
print("Please choose 1 to 6.")
print("Goodbye!")
main()

Part 2: main() and the call that starts the program. main() owns the two lists and passes them to each function. The elif choice != "6" branch catches invalid menu choices.

1) Add a result 2) Show all 3) Statistics
4) Find a skier 5) Sort by time 6) Quit
Choice: 1
Name: Theo
Time in minutes: fast
Please enter a positive number, e.g. 21.5
Time in minutes: 21.75
... (Maya 24.5 and Leila 23 added the same way) ...
Choice: 3
Average: 23.08
Fastest: Theo 21.75
...
Choice: 5
1. Theo 21.75
2. Leila 23.0
3. Maya 24.5

Part of a sample run. The user's typing is shown after each prompt. "fast" is rejected by read_time; choice 3 gives the average of 24.5, 21.75 and 23 (69.25 ÷ 3 ≈ 23.08).

Worked example

Example: A test plan for the whole program

Write test cases that cover every menu choice and the special cases in the specification.

  1. Choice 3 before adding any result → expected "No results yet." (special case: empty lists).
  2. Add Maya 24.5, Theo 21.75, Leila 23, then choice 2 → three numbered lines in the order entered (normal case).
  3. Time input "fast", "-3", "0", "2.1.1" → each rejected with the help message; "21.75" accepted (invalid and boundary input).
  4. Choice 4 with "theo" → "Theo skied 21.75 minutes" (case-insensitive search); with "Sam" → "Not found." (missing target).
  5. Choice 5 → Theo, Leila, Maya with matching times (sorting keeps parallel lists matched).
  6. Choice 9 or "abc" → "Please choose 1 to 6." (invalid menu choice); choice 6 → "Goodbye!".

Every requirement in the specification has at least one test, including the special and invalid cases.

Professional conventions checklist

  • A header comment says what the program is for.
  • Every function has a docstring and a snake_case name that says what it does or returns.
  • 4-space indentation throughout; blank lines between functions.
  • No global variables except constants (in CAPITALS).
  • Processing functions return results; only I/O functions print or read.
  • Remove debugging prints before you hand the program in.
Just for fun

Programmer's estimate: "The last 10% of the program will take 10% of the time."

The reality: the last 10% takes the other 90% of the time. (Testing every special case is part of that last 10%.)

Hands-on activity

Build your own tracker

You need: a computer with Python, paper, and about two hours (spread over a few sessions).

Choose your own data from home or community life, for example: a family's daily step counts, the scores of your favourite hockey team this season, books read and their page counts, or the daily low temperature where you live.

  1. Specify: write 5 or 6 requirements like the ones in this lesson. Include at least: add, show, statistics, search, sort.
  2. Design: draw a structure chart. Write each function's header and docstring before its body.
  3. Build and unit test: write one function at a time and test it with assert or a test loop.
  4. Integrate: write main() with a menu loop.
  5. Validate: write a test plan with expected outputs for every menu choice and special case (empty lists, a missing search target, invalid input), run it, and record actual results.
  6. Reflect: ask a family member to use the program without help. Note anything that confused them and improve the prompts.

What to look for: the program never crashes on bad input, every function has a docstring, and the test plan was written before running.

Your turn

1

Why does main() own the lists instead of making them global?

2

What does find(names, "LEILA") return if names is ["Maya", "Theo", "Leila"]?

3

Is "21.75" accepted by read_time? What about "21,75"?

4

What would happen in choice 3 without the if len(times) == 0: check, when no results have been added?

5

Why does sort_by_time swap two lists each time?

6

Name the algorithm used by each function: find, fastest, sort_by_time.

More practice: warm-up

1

Which menu choice ends the loop?

2

Which functions use input()?

3

What does round(23.08333, 2) give?

4

What does the docstring of a function describe?

5

What is a header comment?

More practice: on your own

1

Write a function slowest(values) that returns the index of the largest time, and add it to choice 3.

2

Add a menu choice that deletes a skier by name. Which functions can you reuse?

3

Write test cases for your delete option.

4

The club wants times shown as minutes and seconds, e.g. 21.75 → 21 min 45 s. Write format_time(minutes).

5

Explain why read_time uses a while True: loop with a return inside.

Challenge

1

Add a choice that prints how many skiers finished in each 5-minute band (15 to under 20, 20 to under 25, 25 to under 30). Use a tally list.

2

The name list may contain two skiers with the same name. How would find behave, and how could you change it to return every matching index?

3

The test plan found no crashes, but the program still accepts some data it should not. Name two weaknesses and how to fix each.

Check your understanding

Quiz

1.

In the tracker, which function is a linear search?

2.

Why is times passed as an argument to average instead of being global?

3.

What does the program print for choice 3 when no results have been added?

4.

Which input does read_time accept?

5.

After sorting, names[0] should be:

6.

Which test case checks a special case of the specification?

For parents and teachers

Big idea: This project lesson brings together A1.6 (arrays), A2.3 (search, max/min and sort algorithms), A3.1 and A3.2 (using and writing subprograms with parameters and local scope), A4.2 (professional conventions and internal documentation) and A4.5 (validating with a full range of test cases).

How to help: Be the "client". Help your teen choose real data for their own tracker, then use the finished program without help and report anything confusing. Ask to see the test plan with expected outputs written before running.

Common difficulties:

  • Trying to write the whole program at once. Encourage one function at a time, tested immediately.
  • Forgetting to update both parallel lists in add, delete or sort.
  • Crashes on empty lists or invalid input; these are exactly what the test plan should catch.
  • Using global lists because it seems easier. It becomes harder as soon as there is a bug.