# 9. Putting Things Together

What have we seen so far?

Values

Types

Variables

Print

Input

Functions

Booleans

Logic

If/Else

Each of the individual topics may feel simple on their own

The difficult part tends to be when putting these topics together to solve complex problems

## 9.1. Writing Bigger Programs

There is no single correct way to write programs, but there are some strategies

For now I recommend a bottom up, incremental approach

Start with an empty function and have it return some arbitrary constant value (e.g.,

`0`

)Run the function and verify that it does what you expect

Add one or two lines of code

Run the function and verify that it does what you expect

Repeat

This incremental strategy is great because a lot of the problem solving you will be doing will be incremental

Additionally, it helps you make sure everything is working along the way

If everything

*was*working and you added two lines of code and suddenly it stops working, perhaps the issue is with the two new lines you wrote

We would still want to write tests for our completed functions, but you may find it difficult to debug a whole complete function when compared to one or two lines of code

## 9.2. Car Rental

Here we solve a bigger problem than we are used to, but we will follow the incremental approach

In fact, we will take it to another extreme

Instead of just writing a few lines of code, we will make each part a function we can test easily, regardless of how “simple” the part seems

**Problem**

A car rental place needs our help. They want a program to calculate how much a customer is to be charged based on their rental agreement, age, how far they drove, and how long they had the car.

We will get and record the customer’s:

Age

Rental agreement classification code (B or D)

Number of days rented

Starting odometer reading

Ending odometer reading

If the classification code is

**B**Base charge of $20.00/day

Plus $0.30 for every km driven

If the classification is

**D**Base charge of $50.00/day

Plus $0.30 for every km driven above the 100km/day average allowance

All renters under the age of 25 are charged an additional $10.00/day

### 9.2.1. An Incremental Solution

**Step 1**

Read the problem

**Step 2**

Understand the problem

This cannot be understated — this is a big part of solving any problem

Half of the description is IO

We know how to do this, so we will start here

The other half of the description is the calculation

**Step 3**

Chip away at the problem

Note

Understand that the example below is only one possible implementation of a solution to this problem. There is literally an infinite number of ways one could go about solving this problem.

#### 9.2.1.1. Input

```
1age = int(input('Age: '))
2classification = input('Classification Code: ')
3number_of_days = int(input('Number of Days Rented: '))
4starting_kms = float(input('Odometer reading at start: '))
5ending_kms = float(input('Odometer reading at end: '))
6
7total_charge = # Some function to do the total charge calculation
8
9print('The total charge is: ' + str(total_charge))
```

Note

In the above example, we would want to verify it is doing what we expect. Since user input is a little difficult to
test with `assert`

, we can simply `print`

out the data and confirm that it is doing what we expect. For example,
the following code could be added to the above example.

```
1print(age, type(age))
2print(classification, type(classification))
3print(number_of_days, type(number_of_days))
4print(starting_kms, type(starting_kms))
5print(ending_kms, type(ending_kms))
```

The above example of reading user input seems to be sufficient for what we need; however, obviously we are far from solving the problem

Line 7 is currently non-functional; it is simply a placeholder for the actual

`total_charge`

calculationIf you were to run the example code, it would not work since

`total_charge`

is currently not being assigned to anything

In other words, we need to actually write some function to do the actual calculation for us

The calculation may seem intimidating, but let’s take the same approach as above

We will write the code we can and leave comments for the parts we still need to tackle

#### 9.2.1.2. Calculating The Total Charge

```
1def calculate_total_charge(some_number_of_parameters):
2
3 # Set up a variable for our total charge
4 total_charge = 0
5
6 # Calculate the number of kilometres traveled.
7 total_kms_traveled =
8
9 # Calculate the average number of kilometers travelled per day
10 average_kms =
11
12 # Calculate the charge based on rental code
13 if rental_code == 'B':
14 # Base charge of $20.00/days + $0.30 for every km driven
15 else:
16 # Base charge of $50.00/days + $0.30 for every km driven above the 100km/day average allowance
17 num_kms_above_allowance =
18
19 # if they're under 25, add additional charge
20 if something :
21
22 # Return the final total charge
23 return some_total_charge
```

Although it may feel like this is a rather silly function so far, it did help us outline what we need to know in order to solve the problem

We need to know the total kilometers travelled

We need to know the average kms/day

We need to know the number of kms driven above the 100km/day average allowance

We need to do the actual rental agreement classification calculation

We need to add the extra charge for people under 25

#### 9.2.1.3. Total Kilometers

A function to calculate the total number of kms

What do we know?

Odometer readings

```
1def total_kms(odometer_start: float, odometer_finish: float) -> float:
2 """
3 This function calculates the total number of kilometers driven based
4 on starting and ending odometer readings.
5
6 @rtype: float
7 @param odometer_start: The number of kms the car had before renting
8 @param odometer_finish: The number of kms the car had after rending
9 @return: The total kms driven
10 """
11
12 return odometer_finish - odometer_start
13
14assert 0 == total_kms(0, 0)
15assert 100 == total_kms(0, 100)
16assert -100 == total_kms(100, 0)
17assert 100.5 == total_kms(100.5, 201)
```

You may be thinking that turning this simple sub-problem (calculating the total kilometers) into a function is overkill

Perhaps you are right

But, it’s also really straightforward to confirm correctness of this function

It is solving an important sub-problem

It is facilitating our incremental development approach

Although the functionality and purpose of

`odometer_finish - odometer_start`

is by no means difficult to understand,`total_kms`

is even clearer

#### 9.2.1.4. Average Kilometers Per Day

A Function to calculate the daily average number of kms

What do we know?

We have a function to calculate the total kms

We also know the number of days the car was rented

```
1def average_kms_per_day(num_days: float, num_kms: float) -> float:
2 """
3 Calculate the average number of kilometers driven per day
4 over the rental period
5
6 @rtype: float
7 @param num_days: The total number of days the car was rented
8 @param num_kms: The total number of kilometers driven during the rental period
9 @return: The average number of kilometers driven per day
10 """
11
12 return num_kms / num_days
13
14
15assert 0 == average_kms_per_day(1, 0)
16assert 1 == average_kms_per_day(1, 1)
17assert -1 == average_kms_per_day(-1, 1)
18assert 0.5 == average_kms_per_day(3, 1.5)
```

#### 9.2.1.5. Kilometers Above Allowable Average

Now for something a little harder

Number of kms over the daily average allowance

What do we know?

Average kms/day given the function

`average_kms_per_day`

we wrote

```
1def num_kms_above_average(avg_num_kms: float) -> float:
2 """
3 Calculates the number of kms the renter went over of their daily allowance.
4 We will use the customer's average daily kms.
5
6 @rtype: float
7 @param avg_num_kms: average number of kms driven per day
8 @return: The number of kms over 100 they went (return 0 if it's less than 100)
9 """
10
11 # If the average kms traveled is above 100,
12 # return how much above, otherwise zero
13 if avg_num_kms > 100:
14 return avg_num_kms - 100
15 else:
16 return 0
17
18
19assert 0 == num_kms_above_average(100)
20assert 1 == num_kms_above_average(101)
21assert 0 == num_kms_above_average(99)
22assert 100 == num_kms_above_average(200)
```

Note

If you were wondering why `num_kms_above_average`

had the two `return`

statements instead of having only one,
good observation; however, having two vs. one is not any more or less correct — it’s simply different.

Further, there is a good argument for making use of a constant instead of hard coding the `100`

for the daily
average limit. Perhaps something like `AVERAGE_DAILY_LIMIT`

. Or maybe have the function include another parameter
for the limit as that would make it far more general.

Remember, with these small differences discussed, one is not more correct than the other. There is literally an infinite number of ways one could go about solving this problems.

#### 9.2.1.6. Revisit Calculating the Total Charge

With the functions we wrote, solving the big

`calculate_total_charge`

becomes simpler

```
1def calculate_total_charge(num_days: float, age: float, rental_code: str, odometer_start: float, odometer_finish: float) -> float:
2 """
3 Calculate how much the renter needs to be charged based on the classification,
4 the number of kms travelled and the age of the driver.
5
6 @rtype: float
7 @param num_days: Number of days the car was rented.
8 @param age: Age of the driver.
9 @param rental_code: The classification code (B ord D).
10 @param odometer_start: Odometer when the renter took the car.
11 @param odometer_finish: Odometer when the renter returned the car.
12 @return: The amount to charge the renter.
13 """
14 # Set up a variable for our total charge
15 total_charge = 0
16
17 # Calculate the number of kilometres traveled.
18 total_kms_traveled = total_kms(odometer_start, odometer_finish)
19
20 # Calculate the average number of kilometers travelled per day
21 average_kms = average_kms_per_day(num_days, total_kms_traveled)
22
23 if rental_code == "B":
24 total_charge = 20.00 * num_days + 0.30 * total_kms_traveled
25 else:
26 total_charge = 50.00 * num_days + 0.30 * num_kms_above_average(average_kms)
27
28 # if they're under 25, add additional charge
29 if age < 25:
30 total_charge += 10 * num_days
31
32 # Return the final total charge
33 return total_charge
34
35
36assert 20 == calculate_total_charge(1, 30, "B", 0, 0)
37assert 50 == calculate_total_charge(1, 30, "D", 0, 0)
38assert 30 == calculate_total_charge(1, 20, "B", 0, 0)
39assert 60 == calculate_total_charge(1, 20, "D", 0, 0)
40assert 50 == calculate_total_charge(1, 30, "B", 0, 100)
41assert 50 == calculate_total_charge(1, 30, "D", 0, 100)
42assert 60 == calculate_total_charge(1, 20, "B", 0, 100)
43assert 60 == calculate_total_charge(1, 20, "D", 0, 100)
44assert 190 == calculate_total_charge(2, 30, "B", 0, 500)
45assert 145 == calculate_total_charge(2, 30, "D", 0, 500)
46assert 210 == calculate_total_charge(2, 20, "B", 0, 500)
47assert 165 == calculate_total_charge(2, 20, "D", 0, 500)
```

Take the time to go over all the parts of this function

If any part feels intimidating, slow down

The new functions were used to simplify much of the calculation

The

`if`

for the rental classification simply evaluates the corresponding cost calculationThe

`if`

for the age adds an additional $10/day

Let’s try: Google colab.

Note

There was nothing stopping us from writing a function for the rental classification calculation or the age calculation. If you feel that would be better, then I would encourage you to do that. Again, assuming your implementation does what is required, it would not be any more or less correct than this implementation.

Activity

Think about how you would write this differently

Would you use all the same functions?

Would you change how the functions worked?

Would you move where you called the functions?

Would you add additional functions?

Would you use constants? Where?