Functie als argument

Deze pagina bevat uitvoerbare code.

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

sorted(L)

een nieuwe lijst met de elementen van L, van klein naar groot

max(L)

het grootste element van L

min(L)

het kleinste element van L

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

longest_song

["Tram 4", 251]

by_length[0]

["Zomer", 187]

longest_title

["Tram 4", 251]

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

count_matching([3, -1, 0, 7], lambda x: x > 0)

2

count_matching(["kat", "olifant", "ezel"], lambda s: len(s) > 3)

2

count_matching([], lambda x: x > 0)

0

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.