Extra

Deze pagina bevat uitvoerbare code.

Een toernooi op de spelletjesavond

Vier vrienden houden een toernooi Vier op een rij. Iedereen speelt één keer tegen iedereen, en elke wedstrijd bestaat uit een paar potjes. Aan het eind van de avond willen ze weten hoe het ervoor staat: wie heeft hoeveel punten, en hoe ging elke wedstrijd?

Dit is een ander soort gegevens dan de teksten van de basis. Het toernooi zit in een lijst van tuples, en de antwoorden worden sets, dictionaries met lijsten erin en een lijst van lijsten. Dat vraagt comprehensions die meer dan één vorm combineren.

De uitslagen

Elke wedstrijd is een tuple van vier: de twee spelers, en hoeveel potjes elk van de twee won. ("Anne", "Bram", 2, 1) betekent dat Anne twee potjes won en Bram één. De punten gaan zo:

Uitslag voor een speler

Punten

meer potjes gewonnen dan de tegenstander

3

evenveel

1

minder

0

Na die wedstrijd heeft Anne dus 3 punten en Bram 0.

In de cel hieronder staan de uitslagen van het toernooi in results, de namen op een vaste volgorde in names, en een functie points(result, player) die de punten van één speler in één wedstrijd teruggeeft. Voer die cel eerst uit.

results = [
    ("Anne", "Bram", 2, 1),
    ("Cas", "Daan", 3, 1),
    ("Anne", "Cas", 2, 2),
    ("Bram", "Daan", 2, 0),
    ("Anne", "Daan", 1, 1),
    ("Bram", "Cas", 3, 2),
]

names = ["Anne", "Bram", "Cas", "Daan"]


def points(result, player):
    """Geeft de punten van player in de wedstrijd result: 3, 1 of 0."""
    first, _, wins_first, wins_second = result

    if player == first:
        own, other = wins_first, wins_second
    else:
        own, other = wins_second, wins_first

    if own > other:
        return 3
    elif own == other:
        return 1
    else:
        return 0

Wat je gaat maken

Stap

Functie

Doet

Resultaat

1

players

alle spelers van het toernooi

set

2

points_per_player

per speler de punten van elke wedstrijd

dictionary van lijsten

3

standings

per speler het totaal

dictionary

4

match_points

de punten van de ene speler tegen de andere

getal

5

cross_table

alle wedstrijden in één tabel

lijst van lijsten

6

print_table

die tabel afdrukken

niets

Stap 1 tot en met 5 zijn elk één comprehension of één uitdrukking.

Stap 1: players(results)

Geeft de set van alle spelers die in results voorkomen.

Aanroep

Resultaat

players(results)

{"Anne", "Bram", "Cas", "Daan"}

players([("Anne", "Bram", 2, 1)])

{"Anne", "Bram"}

players([])

set()

Hint

Een tuple van vier pak je uit in vier variabelen. Wat je niet nodig hebt, heet _: for first, _, _, _ in results. Maak zo één set met de eerste spelers en één met de tweede, en neem de unie uit week 1.

# jouw oplossing
assert players(results) == {"Anne", "Bram", "Cas", "Daan"}
assert players([("Anne", "Bram", 2, 1)]) == {"Anne", "Bram"}
assert players([]) == set()

Stap 2: points_per_player(results)

Geeft een dictionary die elke speler koppelt aan de lijst van zijn punten, één getal per gespeelde wedstrijd, in de volgorde van results.

Aanroep

Resultaat

points_per_player(results)["Anne"]

[3, 1, 1]

points_per_player(results)["Daan"]

[0, 0, 1]

points_per_player([])

{}

Hint

Dit is een dict comprehension over de spelers uit stap 1. De waarde bij elke speler is zelf een list comprehension over results, met een filter op de wedstrijden waarin die speler meedeed.

# jouw oplossing
assert points_per_player(results) == {
    "Anne": [3, 1, 1],
    "Bram": [0, 3, 3],
    "Cas": [3, 1, 0],
    "Daan": [0, 0, 1],
}
assert points_per_player([]) == {}

Stap 3: standings(results)

Geeft een dictionary die elke speler koppelt aan zijn totaal aantal punten.

Aanroep

Resultaat

standings(results)

{"Anne": 5, "Bram": 6, "Cas": 4, "Daan": 1}

standings([])

{}

Hint

Loop het resultaat van stap 2 langs met .items().

# jouw oplossing
assert standings(results) == {"Anne": 5, "Bram": 6, "Cas": 4, "Daan": 1}
assert standings([]) == {}

Stap 4: match_points(results, a, b)

Geeft de punten die speler a haalde in zijn wedstrijd tegen speler b, of 0 als ze niet tegen elkaar speelden.

Aanroep

Resultaat

match_points(results, "Anne", "Bram")

3

match_points(results, "Bram", "Anne")

0

match_points(results, "Anne", "Eva")

0

Hint

set(result[:2]) is de set van de twee spelers van een wedstrijd. Vergelijk je die met {a, b}, dan vind je de wedstrijd tussen a en b, ongeacht wie er eerst staat. Neem de som van een list comprehension. Er is hoogstens één wedstrijd die past, dus de som is de punten uit die wedstrijd, of 0.

# jouw oplossing
assert match_points(results, "Anne", "Bram") == 3
assert match_points(results, "Bram", "Anne") == 0
assert match_points(results, "Cas", "Anne") == 1
assert match_points(results, "Anne", "Eva") == 0

Stap 5: cross_table(results, names)

Een kruistabel zet alle wedstrijden in één overzicht. Er is een rij en een kolom voor elke speler, in de volgorde van names. In rij a en kolom b staan de punten die a tegen b haalde. Tegen zichzelf speelt niemand; daar staat "-".

Geeft de kruistabel als lijst van lijsten. Voor het toernooi is dat:

Anne

Bram

Cas

Daan

Anne

-

3

1

1

Bram

0

-

3

3

Cas

1

0

-

3

Daan

1

0

0

-

Hint

Dit is een geneste comprehension: een rij per speler a, en in elke rij een waarde per speler b. Die waarde kies je met een conditionele expressie: "-" als a en b dezelfde zijn, en anders match_points(results, a, b).

# jouw oplossing
assert cross_table(results, names) == [
    ["-", 3, 1, 1],
    [0, "-", 3, 3],
    [1, 0, "-", 3],
    [1, 0, 0, "-"],
]
assert cross_table([], []) == []

Stap 6: print_table(table, names)

Drukt de kruistabel af, met voor elke rij de naam ervoor. Geeft niets terug.

Anne ['-', 3, 1, 1]
Bram [0, '-', 3, 3]
Cas [1, 0, '-', 3]
Daan [1, 0, 0, '-']

Dit wordt een lus, want afdrukken levert geen nieuwe verzameling op. Gebruik zip om elke naam bij zijn rij te zetten.

# jouw oplossing
print_table(cross_table(results, names), names)

Tot slot

Bram heeft de meeste punten, al won Anne haar wedstrijd tegen hem. In de kruistabel zie je waarom: Bram won zijn andere twee wedstrijden, Anne speelde er twee gelijk. Tel je de getallen in een rij van de kruistabel op, dan krijg je het totaal uit stap 3.

Vooruitblik: de ranglijst

standings zegt hoeveel punten iedereen heeft, maar zet de spelers niet op volgorde. Voor een ranglijst moet je sorteren op het aantal punten. Met wat je nu kent, kost dat een flinke lus. In week 4 van Programmeren 2 leer je hoe het korter kan. Zo ziet het eruit:

totals = standings(results)
ranking = sorted(totals, key=totals.get)

sorted zet de spelers op volgorde, van weinig naar veel punten. key= zegt waarop: op wat totals.get bij elke speler teruggeeft, dus zijn punten. totals.get staat er zonder haakjes. Je roept de methode niet zelf aan, maar geeft haar mee aan sorted, die haar bij elke speler aanroept. Een functie als argument meegeven is het onderwerp van week 4 van Programmeren 2. Je hoeft het hier niet te schrijven.