Python Arena
Step 4 of 5

Read the file once, use the data many times

A file handle reads forward. A for loop over it moves through the file line by line, and when the loop ends, the handle is at the end of the file. A second loop over the same handle finds no lines left, so its body doesn't run at all. Python doesn't stop with an error: the program just gives a wrong answer, which makes this mistake easy to miss.

The file laps.txt
95
88
102
91
with open("laps.txt") as file:
    laps = 0
    for line in file:
        laps += 1
    total = 0
    for line in file:
        total += int(line)
print("Laps:", laps)
print("Total time:", total)
Output
Laps: 4
Total time: 0

There are two ways to fix it. You can open the file again for the second loop, with a second with open(...). Usually it's better to read the file once and store its contents in a list or a dictionary. A list can be looped over as many times as you like, and the rest of the program never touches the file again. With the same file:

The file laps.txt
95
88
102
91
laps = []
with open("laps.txt") as file:
    for line in file:
        laps.append(int(line))

print("Laps:", len(laps))
print("Total time:", sum(laps))
print("Fastest:", min(laps))
for lap in laps:
    if lap > 100:
        print("Slow lap:", lap)
Output
Laps: 4
Total time: 376
Fastest: 88
Slow lap: 102

Reading into a dictionary

When each line is about something with a name, a dictionary is a good home for the data. In this file, each line is one customer's shopping basket: the customer's name, then everything in the basket. A function reads the file into a dictionary whose keys are the customers and whose values are lists of their items, and returns it. After that, the program asks the dictionary as many questions as it likes:

The file baskets.csv
Mia;bread;milk;apples
Noah;coffee;bananas
Lena;apples;bread;cheese;milk
def read_baskets(filename):
    baskets = {}
    with open(filename) as file:
        for line in file:
            parts = line.strip().split(";")
            baskets[parts[0]] = parts[1:]
    return baskets


baskets = read_baskets("baskets.csv")
print(baskets["Noah"])
for customer, items in baskets.items():
    print(f"{customer}: {len(items)} items")
for customer, items in baskets.items():
    if "bread" in items:
        print(customer, "bought bread")
Output
['coffee', 'bananas']
Mia: 3 items
Noah: 2 items
Lena: 4 items
Mia bought bread
Lena bought bread

The slice parts[1:] is already a list: every field after the name. Here the fields are words, so the list can go into the dictionary as it is. When they are numbers, build the list with a loop that converts each field with int, as the rounds example in the previous step did, and store that list under the name. Then the numbers are converted once, while reading, and every later calculation can use them.

Two loops over the dictionary are no problem, unlike two loops over the file. And the rest of the program never sees a semicolon or a line break: only read_baskets knows what the file looks like.

Your task

Challenge 1 of 3

The program should print the number of days in the file temperatures.txt and their average temperature, but the average comes out wrong. Fix it, so that with this file

12
15
17
13
14

it prints:

Days: 5
Average: 14.2

Challenge 2

Each line of the file ratings.csv has the title of a movie and the ratings people gave it, from 1 to 5. Write a function read_ratings(filename) that returns a dictionary whose keys are the titles and whose values are lists of the ratings as integers.

With this file

Moon Rocks;4;5;3
The Long Road;2;3
Cats in Space;5;5;4;5

print(read_ratings("ratings.csv")) prints:

{'Moon Rocks': [4, 5, 3], 'The Long Road': [2, 3], 'Cats in Space': [5, 5, 4, 5]}

Challenge 3

The starter code has a ready read_ratings function from the previous task. Write the function best_rated(filename), which uses it and returns the title with the highest average rating. You can assume no two titles have the same average.

With this ratings.csv

Moon Rocks;4;5;3
The Long Road;2;3
Cats in Space;5;5;4;5

print(best_rated("ratings.csv")) prints:

Cats in Space