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)
15
23
42A 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)))
['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)
Tasks left: 2The 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)
Tasks left: 2
COOK
CALL GRANDMAfilter 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.