Python Arena
Step 4 of 4

A collection you can search

A larger program often has two kinds of classes: one for a single thing, and one for a collection of those things. Here a Hike knows its own data and how to show itself, and a Logbook keeps the hikes someone plans to walk and answers questions about them:

class Hike:
    def __init__(self, name, km):
        self.name = name
        self.km = km
        self.walked = False

    def __str__(self):
        status = "walked" if self.walked else "not yet"
        return f"{self.name} ({self.km} km, {status})"


class Logbook:
    def __init__(self):
        self.hikes = []

    def add(self, name, km):
        self.hikes.append(Hike(name, km))

    def mark_walked(self, name):
        for hike in self.hikes:
            if hike.name == name:
                hike.walked = True

    def find(self, word):
        found = []
        for hike in self.hikes:
            if word.lower() in hike.name.lower():
                found.append(hike)
        return found

    def km_left(self):
        total = 0
        for hike in self.hikes:
            if not hike.walked:
                total += hike.km
        return total


summer = Logbook()
summer.add("Lake Path", 5)
summer.add("Ridge Loop", 12)
summer.add("Old lake trail", 9)
summer.mark_walked("Lake Path")

for hike in summer.find("lake"):
    print(hike)
print("Km left:", summer.km_left())
Output
Lake Path (5 km, walked)
Old lake trail (9 km, not yet)
Km left: 21

A few things to notice:

  • The client only calls add with a name and a length. Logbook makes the Hike objects itself and keeps them in its list.
  • find is a search: it goes through the list and collects the hikes that match into a new list. It compares in small letters, so lake also finds Lake.
  • find returns the Hike objects themselves, so the client can print them (with their __str__) or use their attributes.
  • mark_walked changes the attribute of a hike that is in the list. The list holds references to the hikes, so the change stays.

Searching for the best match

Another common search finds the one item that is best by some measure. Such a method goes through the list once and remembers the best item so far. If the list can be empty, it needs an answer for that too:

def longest(self):
    if len(self.hikes) == 0:
        return None
    best = self.hikes[0]
    for hike in self.hikes:
        if hike.km > best.km:
            best = hike
    return best

The examples of this part have grown step by step into something like real programs: classes that keep their data valid, show themselves with __str__, and answer questions about what they hold.

Your task

Challenge 1 of 3

What does this program print? Type each line it prints.

Challenge 2

The starter code has a class Album and a class Collection that keeps albums in a list. Write two search methods for Collection:

  • by_artist(artist) returns a list of the albums by that artist, in the order they were added.
  • from_years(first, last) returns a list of the albums released from the year first to the year last, both included.

Both return the Album objects themselves. This code:

shelf = Collection()
shelf.add("Blue", "Joni Mitchell", 1971)
shelf.add("Kind of Blue", "Miles Davis", 1959)
shelf.add("Court and Spark", "Joni Mitchell", 1974)
for album in shelf.by_artist("Joni Mitchell"):
    print(album)
for album in shelf.from_years(1950, 1971):
    print(album)

prints

Blue by Joni Mitchell (1971)
Court and Spark by Joni Mitchell (1974)
Blue by Joni Mitchell (1971)
Kind of Blue by Miles Davis (1959)

Challenge 3

Write a class TodoList that keeps tasks as strings. Give it these methods:

  • add(task) puts a task at the end of the list.
  • done(task) removes the task and returns True, or returns False when there is no such task.
  • search(word) returns a list of the tasks that have the word in them, whatever the capitals, in their order in the list.
  • __str__ returns no tasks when the list is empty, and otherwise the word Tasks, the number of tasks in parentheses, a colon and the tasks separated by commas, as shown below.

This code:

todo = TodoList()
todo.add("Buy milk")
todo.add("Call grandma")
todo.add("Buy stamps")
print(todo)
print(todo.search("buy"))
print(todo.done("Buy milk"), todo.done("Swim"))
print(todo)

prints

Tasks (3): Buy milk, Call grandma, Buy stamps
['Buy milk', 'Buy stamps']
True False
Tasks (2): Call grandma, Buy stamps