Python Arena
Step 2 of 3

Selecting with filter

map changes every item. filter keeps some of the items as they are and drops the rest. You write it as filter(function, series): it calls the function on each item and keeps the items for which the function returns True:

numbers = [8, 3, 15, 4, 23, 42]
for number in filter(lambda n: n > 10, numbers):
    print(number)
Output
15
23
42

A named function works just as well, and a longer condition often reads better with a name:

def is_palindrome(word):
    return word == word[::-1]


words = ["level", "python", "radar", "map", "noon"]
print(list(filter(is_palindrome, words)))
Output
['level', 'radar', 'noon']

That's the same as the comprehension [word for word in words if is_palindrome(word)].

filter gives an iterator too

Like map, filter returns an iterator, which is used up after one pass. That can bite when you use the result twice, for example to count the items and then to print them:

class Task:
    def __init__(self, name, done):
        self.name = name
        self.done = done


tasks = [Task("shop", True), Task("cook", False), Task("call Grandma", False)]
open_tasks = filter(lambda task: not task.done, tasks)
print("Tasks left:", len(list(open_tasks)))
for task in open_tasks:
    print(task.name)
Output
Tasks left: 2

The loop printed nothing: list(open_tasks) already took every item, so nothing was left for the loop. Store the list once and use it as often as you like:

class Task:
    def __init__(self, name, done):
        self.name = name
        self.done = done


tasks = [Task("shop", True), Task("cook", False), Task("call Grandma", False)]
open_tasks = list(filter(lambda task: not task.done, tasks))
print("Tasks left:", len(open_tasks))
for name in map(lambda task: task.name.upper(), open_tasks):
    print(name)
Output
Tasks left: 2
COOK
CALL GRANDMA

filter and map together

The second program also used map on what filter kept. Both take a series and give a series, so one's result can go straight into the other. Such a chain is read from the inside out:

names = list(map(lambda task: task.name, filter(lambda task: not task.done, tasks)))

First filter keeps the open tasks, then map takes their names. The comprehension [task.name for task in tasks if not task.done] does the same in one go.

Your task

Challenge 1 of 3

Fill in the blank with the condition, so that filter keeps only the numbers that are divisible by 3. The program should print:

[9, 21, 3, 30]

Challenge 2

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

Challenge 3

The class Book holds a book's title and whether it's on loan. Write a function available_titles(books) that returns a list of the titles of the books that are not on loan, in alphabetical order. Use filter and map. For example,

books = [Book("Matilda", False), Book("Dracula", True), Book("Beloved", False)]
print(available_titles(books))

should print:

['Beloved', 'Matilda']