Embercode
✕
ConditionalsLesson 5 of 14

60 min read

Module assignment: Ferry booking desk in Python

Why this assignment

Each lesson in this module taught one tool: if, elif and else, the operators and, or and not, chained comparisons such as 0 <= age <= 120, the match statement, the one-line conditional expression, and the standard decision problems. Real programs use all of them together, and they also need the habit of checking input before trusting it. This assignment asks you to build one small program that does that. It is a ferry booking desk. It reads three bookings, rejects bad ones with a clear message, prices the good ones and prints a total.

You will not be asked anything new. You will use the rules you learned, in the order you learned to apply them: guard clauses first, specific rules before general ones, and boundary values checked by hand before you run the program.

Plan on about an hour. Read the whole brief once, write the validation first, then the prices, then the output. Test as you go.

The story

A clerk types three bookings into the desk computer. For each booking the computer must say whether the booking is valid, and if it is valid, print the day it sails and the fare. At the end it prints the total of all valid fares.

Each booking has three parts. The passenger's age. The vehicle they bring, which is N for none, B for a bicycle or C for a car. The day of the week the ferry sails, as a number from 1 to 7, where 1 is Monday and 7 is Sunday.

The company wants the program to run exactly once per session, with three bookings. You do not need loops for this assignment. Read each booking with one call to input() and split(), as the starter shows, and handle each booking in order.

Input and output

The input has exactly three lines. Each line has three values separated by spaces:

age vehicle day
  • age is a whole number.
  • vehicle is one capital letter: N, B or C.
  • day is a whole number. Use 1 for Monday up to 7 for Sunday.

The output has four lines. The first three lines are one line per booking, in the order they were typed. The fourth line is the total.

For a valid booking, the line is:

Booking 1: Monday, fare 0

The word after the colon is the day name, then the fare. For an invalid booking, the line is only the error message after the colon:

Booking 2: invalid day

The last line is:

Total: 35

The total is the sum of the fares for valid bookings only. An invalid booking adds nothing.

The starter below reads the three bookings and prints them back. It proves your input reading works before you write any rules. Save the code as main.py, run it with python3 main.py, and type the three lines from Example 1 when the program waits for input. Check the output. Then change the code.

def read_booking():
    age_text, vehicle, day_text = input().split()
    return int(age_text), vehicle, int(day_text)
 
 
age1, vehicle1, day1 = read_booking()
print(f"Read 1: age {age1}, vehicle {vehicle1}, day {day1}")
 
age2, vehicle2, day2 = read_booking()
print(f"Read 2: age {age2}, vehicle {vehicle2}, day {day2}")
 
age3, vehicle3, day3 = read_booking()
print(f"Read 3: age {age3}, vehicle {vehicle3}, day {day3}")
 
# Your validation, pricing and total go here.

With Example 1 typed in, the starter prints:

Read 1: age 2, vehicle N, day 1
Read 2: age 30, vehicle C, day 6
Read 3: age 70, vehicle B, day 7

The starter uses built-in parts of Python, which come with the language and need no install. input() waits for you to type a line and returns it as text. split() cuts that text into pieces at each space, so split() on 2 N 1 gives the three pieces 2, N and 1. int() turns text such as 2 into a number. The vehicle stays as text, because it is a letter.

The rules

Read every rule in this section before you write any code. Each one is a sentence you can turn into a condition.

Validation. Check the three parts of each booking in this order, and stop at the first failure:

  1. The age must be from 0 to 120 inclusive. Inclusive means both end values count, so 0 and 120 are allowed. If the age is not in that range, the message is invalid age.
  2. The vehicle must be N, B or C. Lower-case letters are not accepted. If it is not, the message is invalid vehicle.
  3. The day must be from 1 to 7 inclusive. If it is not, the message is invalid day.

The order matters. A booking with both a bad age and a bad day prints invalid age, because age is checked first. This is the same idea as the guard clauses in the classic problems lesson.

Passenger fare. Price the passenger from the age:

  • Age 0, 1 or 2: the passenger fare is 0.
  • Age 3 to 15 inclusive, or 65 and older: the passenger fare is 5.
  • Any other age: the passenger fare is 10.

Test the boundaries of these bands. An age of 2 is free, 3 is a child, 15 is a child, 16 is an adult, 64 is an adult and 65 is a senior.

Vehicle fare. Add the vehicle fare to the passenger fare:

  • N: 0.
  • B: 2.
  • C: 12.

Weekend surcharge. If the day is Saturday or Sunday, add 3 to the fare. The surcharge applies to every valid booking on those days, including a free passenger with no vehicle. Weekday bookings get no surcharge.

Day names. Print the day name for a valid booking: 1 is Monday, 2 is Tuesday, 3 is Wednesday, 4 is Thursday, 5 is Friday, 6 is Saturday and 7 is Sunday. Use a match statement to choose the name. A match statement is Python's version of the switch statement that some other languages have. Each case lists one value to compare with, and the first case that matches runs. Python has no break to write, because a matched case does not run the cases below it. The match statement needs Python 3.10 or later. This course uses Python 3.14.

Total. Add up the fares of the valid bookings. A fare of 0 is a valid fare, and it still counts in the total as 0. Mark an invalid booking with None, which is Python's word for "no value". Test for a valid booking with is not None, not with if fare:, because Python treats 0 as false inside an if.

Worked examples

Here are two complete runs. Work each one by hand before you check the output.

Example 1

Input:

2 N 1
30 C 6
70 B 7

Output:

Booking 1: Monday, fare 0
Booking 2: Saturday, fare 25
Booking 3: Sunday, fare 10
Total: 35

Trace booking 1. Age 2 is valid, N is valid, day 1 is valid. The passenger fare is 0 because the age is 2. The vehicle fare is 0. Day 1 is a weekday, so there is no surcharge. The fare is 0 and the day is Monday.

Trace booking 2. Age 30 is valid, C is valid, day 6 is valid. The passenger fare for age 30 is 10. The vehicle fare for C is 12. The sum is 22. Day 6 is Saturday, so the surcharge adds 3, giving 25.

Trace booking 3. Age 70 is valid, B is valid, day 7 is valid. The passenger fare for age 70 is 5, because 70 is 65 or older. The vehicle fare for B is 2. The sum is 7. Day 7 is Sunday, so the surcharge adds 3, giving 10.

The total is 0 + 25 + 10, which is 35.

Example 2

Input:

40 X 3
120 N 9
25 B 3

Output:

Booking 1: invalid vehicle
Booking 2: invalid day
Booking 3: Wednesday, fare 12
Total: 12

Booking 1 has a valid age of 40, but X is not one of the vehicle letters, so the program stops at the vehicle check. Booking 2 has a valid age of 120 and a valid vehicle N, but day 9 is out of range. Booking 3 is valid: the age of 25 gives a passenger fare of 10, B gives 2, and day 3 is a weekday, so the fare is 12. The total is 12, because only the third booking counts.

Requirements checklist

Copy this checklist into your own notes and tick each item when your program passes it. A program that misses an item is not finished, even if most of the output is right.

  • The program reads three bookings, each on one line, with an age, a vehicle letter and a day.
  • The age is checked for the range 0 to 120 before anything else, and the message is invalid age.
  • The vehicle is checked for N, B or C only, and lower-case letters are rejected with invalid vehicle.
  • The day is checked for 1 to 7, and the message is invalid day.
  • Passenger fares follow the bands, with boundaries at 2 and 3, at 15 and 16, at 64 and 65.
  • Vehicle fares are 0, 2 and 12 for N, B and C.
  • The weekend surcharge of 3 applies to days 6 and 7 only, and it applies to free passengers too.
  • The day name comes from a match statement, and every day from 1 to 7 prints the right name.
  • Each valid booking prints Booking n: <day name>, fare <fare> in the order given. Write the actual name and number in place of the placeholders.
  • Each invalid booking prints Booking n: <message> with the first failing message only.
  • A fare of 0 is printed as a valid booking, and the code tests is not None, not if fare:.
  • The total at the end counts only valid bookings.
  • The code has no copied blocks that differ by one word. Repeated logic is in a function, or a single block that runs for each booking.
  • You tested every boundary listed in the testing section below, and the output matched your hand trace.

Grading rubric

Your work is graded against the three levels below. Each level includes everything in the level before it.

60 percent: the core works. The program reads three bookings and prints a fare line for each one that is valid, with the correct day name. The passenger band and the vehicle fare are right for the ordinary cases, such as age 30 with N on day 2. The total is printed. Invalid bookings may print the wrong message or none, and the boundaries may be wrong.

80 percent: the rules are complete. Everything in the 60 percent level is correct, and in addition: every boundary of the passenger bands is right, the weekend surcharge is correct on days 6 and 7 and absent on other days, the three validity checks run in the stated order and print the stated messages, and invalid bookings do not change the total. A program that prints correct output for the two worked examples reaches this level.

100 percent: the program is clean and complete. Everything in the 80 percent level is correct, and in addition: the program passes every test in the testing section, the code is easy to read with clear names for values such as passenger_fare and vehicle_fare, the validation is written as guard clauses at the top of the logic, the match statement covers all seven days with no mistakes, and the program does not copy the same block three times without a good reason. A reviewer should be able to find each rule in the code by reading the names.

Partial credit is given for a correct structure with a wrong constant, such as a surcharge of 2 instead of 3. Marks are lost for each wrong output line, not for a single slip that affects every line.

Testing before you submit

Do not rely on the two examples alone. Test each boundary by hand, predict the output, and then run the program. The table below lists the cases to check. Each row is one booking, with the expected line.

Input lineExpected line
2 N 1Booking 1: Monday, fare 0
3 N 2Booking 1: Tuesday, fare 5
15 N 3Booking 1: Wednesday, fare 5
16 N 4Booking 1: Thursday, fare 10
64 N 5Booking 1: Friday, fare 10
65 N 6Booking 1: Saturday, fare 8
-1 N 1Booking 1: invalid age
121 N 1Booking 1: invalid age
30 c 1Booking 1: invalid vehicle
30 B 0Booking 1: invalid day
30 B 8Booking 1: invalid day
30 C 7Booking 1: Sunday, fare 25

Each row is a single booking. Use the first line of your input for the row, and put two sample lines after it for the other two bookings, such as 2 N 1 twice. Then check the first output line. For the 65 N 6 row, the passenger fare is 5 because 65 is a senior, not 10, and the surcharge makes it 8. For 30 C 7, the passenger fare is 10, the vehicle fare is 12 and the surcharge is 3, so the total is 25.

Test one full run with all three bookings invalid. The output should be three error lines and a total of 0. Test one run with all three valid, and check the total by adding the three fares by hand.

Step-by-step hints

Try each step before you read the next one.

Hint 1: start with the input

Use input() to read one line as text, then split() to cut it into the three pieces. Convert the age and the day with int(). Keep the vehicle as text. Print the three values back, to check that the input is read in the right order. Add the other two bookings once the first one prints correctly.

Hint 2: validation is first

Before any price is computed, write three if statements, one per rule, in the order the brief gives. Each one prints its message, returns None and stops the booking. Put the price code after the last check. A booking that fails a check returns early and never reaches the price code. Test each message on its own with one bad value.

Hint 3: the passenger band

Write the bands as an if, elif, else ladder with the smallest band first: age 2 or less, then 3 to 15 or 65 and older, then everything else. A ladder is correct because each test only needs to decide its own band. Check each boundary, 2, 3, 15, 16, 64 and 65.

Hint 4: the vehicle fare

Use a match statement on the vehicle. Each case gives one fare, and case _: gives 0 for N. The _ pattern matches anything that no earlier case matched.

Hint 5: the surcharge and the day name

Add the surcharge with an if that tests day in (6, 7). The in test checks whether a value is one of the items in a tuple, which is a fixed group of values written in round brackets. Keep the surcharge separate from the fares, so you can check it on its own. For the name, write a match statement that returns a string, with one case for each day and a case _: that returns "unknown".

Hint 6: the total

Keep one variable for the total, starting at 0. Return None from the booking function when the booking is invalid. Add a fare only when it is not None. Test this with if fare is not None:. Do not write if fare:, because a fare of 0 is valid and must still be added, and Python treats 0 as false.

Reference solution

Try the assignment first. The solution below is one correct way to write it. It uses four small functions: one for the passenger fare, one for the vehicle fare, one for the day name and one for handling a booking. The handling function prints the line for its booking and returns the fare, or None when the booking is invalid.

Show the answer

def passenger_fare(age):
    if age <= 2:
        return 0
    if 3 <= age <= 15 or age >= 65:
        return 5
    return 10
 
 
def vehicle_fare(vehicle):
    match vehicle:
        case "B":
            return 2
        case "C":
            return 12
        case _:
            return 0
 
 
def day_name(day):
    match day:
        case 1:
            return "Monday"
        case 2:
            return "Tuesday"
        case 3:
            return "Wednesday"
        case 4:
            return "Thursday"
        case 5:
            return "Friday"
        case 6:
            return "Saturday"
        case 7:
            return "Sunday"
        case _:
            return "unknown"
 
 
def handle_booking(number, age, vehicle, day):
    # Prints the line for one booking. Returns its fare, or None when the booking is invalid.
    if not 0 <= age <= 120:
        print(f"Booking {number}: invalid age")
        return None
    if vehicle not in ("N", "B", "C"):
        print(f"Booking {number}: invalid vehicle")
        return None
    if not 1 <= day <= 7:
        print(f"Booking {number}: invalid day")
        return None
 
    fare = passenger_fare(age) + vehicle_fare(vehicle)
    if day in (6, 7):
        fare += 3
    print(f"Booking {number}: {day_name(day)}, fare {fare}")
    return fare
 
 
def read_booking():
    age_text, vehicle, day_text = input().split()
    return int(age_text), vehicle, int(day_text)
 
 
def main():
    total = 0
 
    age, vehicle, day = read_booking()
    fare = handle_booking(1, age, vehicle, day)
    if fare is not None:
        total += fare
 
    age, vehicle, day = read_booking()
    fare = handle_booking(2, age, vehicle, day)
    if fare is not None:
        total += fare
 
    age, vehicle, day = read_booking()
    fare = handle_booking(3, age, vehicle, day)
    if fare is not None:
        total += fare
 
    print(f"Total: {total}")
 
 
if __name__ == "__main__":
    main()

The main function has three blocks that differ only in their numbers and in the booking they handle. That is a fair trade for a program without loops. Once you learn loops, the three blocks become one loop that runs three times.

The last two lines are a common Python pattern. __name__ is a name that Python sets to "__main__" when you run the file directly. The if means main() runs only in that case, not when another file imports this one.

Stretch extension

When you finish the main assignment, try one extension. A fourth line in the input gives a sailing year, and the program prints leap year sailing or common year sailing after the total. Use the leap-year rule from the classic problems lesson, or the standard library function calendar.isleap, which returns True for a leap year. Read the year with int(input()) after the three bookings. Test 2024, 2023 and 1900. The extension does not change the other output lines, so the first four lines must still match the examples.

Common mistakes to avoid

Students who lose marks on this assignment usually make one of these errors. Check your program against each one before you submit.

The first mistake is treating a fare of 0 as invalid. In Python, 0 counts as false inside an if, so if fare: is false for a free passenger. Every free passenger is dropped from the total and the rule is broken. This small program shows the problem:

fare = 0
if fare:
    print("counted")
else:
    print("not counted")

Output:

not counted

Use None for invalid bookings and check fare is not None.

The second mistake is putting the weekend surcharge inside one band of the passenger ladder. The surcharge applies to every valid booking on a weekend, so it belongs after both fares are added. If it sits inside one band, only the passengers in that band pay it.

The third mistake is checking the age band with age > 3 instead of age >= 3. The boundary rules are part of the grade. Test 3 and 15 by hand before you submit.

The fourth mistake is letting an invalid booking reach the price code. If the validation runs after the price, an invalid booking may print a fare line and an error line, and the total may include it. Put every validation check at the top.

The fifth mistake is a match on the vehicle with no case _:. Python does not stop with an error when no case matches. The function reaches its end and returns None, and the error appears later, where the value is used:

def vehicle_fare(vehicle):
    match vehicle:
        case "B":
            return 2
        case "C":
            return 12
 
 
print(10 + vehicle_fare("N"))

Output, which is the last line of the error message:

TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

Validation should reject any other letter before pricing runs, but case _: is still a sensible safety net.

The sixth mistake is testing the vehicle with in on a string instead of a tuple. Python's in also works on text, and it checks whether one piece of text appears inside another. Here is what that does:

print("BC" in "NBC")
print("" in "NBC")

Output:

True
True

So vehicle in "NBC" accepts BC and an empty string. Write vehicle in ("N", "B", "C") instead, with a tuple.

The seventh mistake is a line with the wrong number of values. If a booking line has only two values, such as 30 C, the unpacking (giving each piece its own name) in read_booking fails before any check runs. The last line of the error message is:

ValueError: not enough values to unpack (expected 3, got 2)

Read the last line first. It tells you what went wrong, and the lines above it show where.

What you practised

  • Guard clauses that reject bad input in a stated order, with one message per failure.
  • An if, elif, else ladder for bands, with each boundary tested by hand.
  • A match statement for a fixed set of codes, with case _: as the default.
  • The and and or operators, the not operator and chained comparisons for range checks.
  • A match statement that returns a value, used to name the day.
  • A running total that includes valid results only, with None as the marker for invalid ones.
Practice this topic: coding problems for Conditionals →

Stuck? Ask the tutor

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

Sign in to ask the tutor.

← Classic conditional problems in Python