Functie als argument¶
Een functie krijgt argumenten mee: getallen, strings, lijsten. In dit college geef je een functie zelf mee, als argument aan een andere functie. Daarmee vertel je bijvoorbeeld aan een sorteerfunctie waarop ze moet sorteren.
Je hebt dit misschien al eens gezien. De extra-opgave van
week 2 sloot af met een vooruitblik op een ranglijst:
sorted(totals, key=totals.get). Heb je die opgave niet gedaan, dan is dat geen
probleem: dit college begint bij het begin.
Sorteren, het grootste en het kleinste¶
Python heeft drie ingebouwde functies die je deze week veel gebruikt:
Functie |
Geeft terug |
|---|---|
|
een nieuwe lijst met de elementen van |
|
het grootste element van |
|
het kleinste element van |
max ken je uit het vorige college, met twee
argumenten. Je kunt haar ook één lijst geven.
numbers = [5, 2, 8]
print(sorted(numbers))
print(max(numbers))
print(min(numbers))
[2, 5, 8]
8
2
Strings vergelijkt Python op alfabet: "appel" komt vóór "peer". sorted
geeft een nieuwe lijst terug en laat de oude lijst staan.
fruit = ["peer", "appel", "kiwi", "banaan"]
print(sorted(fruit))
print(max(fruit))
print(fruit)
['appel', 'banaan', 'kiwi', 'peer']
peer
['peer', 'appel', 'kiwi', 'banaan']
Een functie meegeven met key=¶
Stel dat je de vruchten niet op alfabet wilt sorteren, maar op de lengte van hun
naam. Dan geef je sorted een functie mee die bij elk element zegt waarop
gesorteerd wordt. Dat doe je met key=:
print(sorted(fruit, key=len))
print(max(fruit, key=len))
print(min(fruit, key=len))
['peer', 'kiwi', 'appel', 'banaan']
banaan
peer
Let op: len staat er zonder haakjes. Je roept len hier niet zelf aan, je
geeft de functie mee aan sorted. sorted roept len daarna aan bij elk
element en sorteert op wat er terugkomt: 4 voor "peer", 5 voor "appel",
enzovoort. Bij gelijke lengte blijft de oorspronkelijke volgorde staan; daarom
staat "peer" vóór "kiwi".
Dat heet een functie als argument: je geeft een functie mee aan een andere
functie, die haar zelf aanroept. De functie achter key= mag ook een functie zijn
die je zelf schrijft:
def last_letter(s):
"""Geeft de laatste letter van s."""
return s[-1]
print(sorted(fruit, key=last_letter))
['kiwi', 'appel', 'banaan', 'peer']
Ook een methode kun je meegeven. Bij een dictionary geeft totals.get(name) de
waarde bij de sleutel name. Zonder haakjes is totals.get de functie zelf.
Dit is de ranglijst uit de vooruitblik van week 2:
totals = {"Anne": 5, "Bram": 7, "Cas": 4, "Daan": 1}
print(sorted(totals, key=totals.get))
print(max(totals, key=totals.get))
['Daan', 'Cas', 'Anne', 'Bram']
Bram
sorted loopt de sleutels van de dictionary langs, de namen, en sorteert ze op
wat totals.get bij elke naam teruggeeft: de punten.
lambda: een functie zonder naam¶
Vaak is de functie achter key= zo klein dat ze geen eigen def en geen naam
verdient. Dan schrijf je haar ter plekke op, met lambda:
lambda s: s[-1]
Dit is een functie met de parameter s, die s[-1] teruggeeft. Ze doet dus
hetzelfde als last_letter. Tussen lambda en de dubbele punt staat de
parameter; na de dubbele punt staat wat de functie teruggeeft. Een return
schrijf je er niet bij. Zo’n functie heet een lambda-functie.
print(sorted(fruit, key=lambda s: s[-1]))
['kiwi', 'appel', 'banaan', 'peer']
Een lambda-functie is vooral handig bij een lijst van lijsten. Hier staan de spelers als paren: een naam en een aantal punten.
players = [["Anne", 5], ["Bram", 7], ["Cas", 4], ["Daan", 1]]
print(max(players, key=lambda p: p[1]))
print(sorted(players, key=lambda p: p[1]))
print(sorted(players, key=lambda p: -p[1]))
['Bram', 7]
[['Daan', 1], ['Cas', 4], ['Anne', 5], ['Bram', 7]]
[['Bram', 7], ['Anne', 5], ['Cas', 4], ['Daan', 1]]
lambda p: p[1] geeft van elke speler de punten. Daarop zoekt max de winnaar,
en daarop sorteert sorted, van weinig naar veel punten. Wil je de ranglijst van
veel naar weinig, sorteer dan op min de punten: -p[1].
Zelf een functie met een functieparameter¶
Ook in een eigen functie kan een parameter een functie zijn. In de basisopgave
van vorige week schreef je best_word: het woord met de
hoogste Scrabble-score, met dezelfde vorm als largest. best hieronder doet
hetzelfde, maar de score is nu een parameter. Bij score(L[0]) roept best de
functie aan die je hebt meegegeven.
def best(L, score):
"""Geeft het element uit L met de hoogste score; L is niet leeg."""
if len(L) == 1: # basisgeval
return L[0]
rest = best(L[1:], score) # het beste element in de rest van L
if score(L[0]) >= score(rest):
return L[0]
else:
return rest
assert best(fruit, len) == "banaan"
assert best(players, lambda p: p[1]) == ["Bram", 7]
assert best([3, -8, 5], abs) == -8
Dezelfde best zoekt de langste naam, de speler met de meeste punten, of het
getal dat het verst van 0 ligt. Alleen de functie die je meegeeft, verschilt.
Zo werkt max met key= ook.
Terug naar de knapzak¶
subset uit het vorige college gaf de grootste som, maar niet welke spullen
daarbij horen. Laat use it en lose it nu lijsten met spullen teruggeven in
plaats van sommen. Dan kies je de lijst met de grootste som:
max(use_it, lose_it, key=sum). sum ken je uit week 2.
def subset_items(capacity, items):
"""Geeft de items met de grootste som die niet boven capacity uitkomt."""
if items == []: # basisgeval: geen items meer
return []
first = items[0]
rest = items[1:]
if first > capacity: # recursief geval: het eerste item past niet
return subset_items(capacity, rest)
else: # recursief geval: probeer beide keuzes
use_it = [first] + subset_items(capacity - first, rest)
lose_it = subset_items(capacity, rest)
return max(use_it, lose_it, key=sum)
assert subset_items(10, [8, 4, 6]) == [4, 6]
assert subset_items(42, [30, 10, 45, 5]) == [30, 10]
assert subset_items(10, []) == []
De vorm is die van subset. Twee dingen zijn anders: use it plakt
[first] voor de lijst van de rest in plaats van first op te tellen, en max
vergelijkt de twee lijsten op hun som.
Opdrachten¶
Hieronder staat een lijst met nummers van een afspeellijst: de titel en de lengte in seconden. Voer de cel eerst uit.
songs = [["Regen", 214], ["Zomer", 187], ["Tram 4", 251], ["Avond", 199]]
Opdracht 1¶
Schrijf met max, min of sorted en een lambda-functie:
a. longest_song: het nummer dat het langst duurt;
b. by_length: de nummers gesorteerd op lengte, van kort naar lang;
c. longest_title: het nummer met de langste titel.
Variabele |
Waarde |
|---|---|
|
|
|
|
|
|
Hint
Een nummer is een paar [titel, seconden]. lambda song: song[1] geeft de
lengte. De lengte van de titel is len(song[0]).
longest_song = ...
by_length = ...
longest_title = ...
assert longest_song == ["Tram 4", 251]
assert by_length == [["Zomer", 187], ["Avond", 199], ["Regen", 214], ["Tram 4", 251]]
assert longest_title == ["Tram 4", 251]
Opdracht 2¶
Schrijf de functie count_matching(L, test). test is een functie die bij elk
element True of False teruggeeft. count_matching geeft terug voor hoeveel
elementen van L de functie test True teruggeeft.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
Hint
Met een list comprehension uit week 2 maak je een lijst van de elementen x
waarvoor test(x) waar is. Hoe lang is die lijst?
# jouw oplossing
assert count_matching([3, -1, 0, 7], lambda x: x > 0) == 2
assert count_matching(["kat", "olifant", "ezel"], lambda s: len(s) > 3) == 2
assert count_matching(songs, lambda song: song[1] < 200) == 2
assert count_matching([], lambda x: x > 0) == 0
Tot slot¶
Met key= vertel je sorted, max en min waarop ze moeten vergelijken. Je
geeft daarvoor een functie mee: een bestaande zoals len, een eigen functie, of
een lambda-functie die je ter plekke opschrijft. En in een eigen functie kan een
parameter zelf een functie zijn, die je aanroept zoals elke andere functie.
Met max(use_it, lose_it, key=sum) komen de twee onderwerpen van deze week
samen: use it or lose it probeert beide keuzes, en key= zegt welke de beste
is.