Beetje Coderen

Les 5

Lijsten, tuples en sets

De juiste collectie kiezen: volgorde, onveranderlijkheid of uniciteit.

⏱ 45–60 minuten leestijd + oefenen

🎯 Na deze les kun je

  • het verschil tussen list, tuple en set uitleggen
  • setbewerkingen gebruiken
  • tuple-unpacking toepassen

Python heeft drie ingebouwde manieren om een reeks waarden te bewaren: lijsten, tuples en sets. Ze lijken op elkaar, maar de keuze maakt verschil. Kies je goed, dan is je code duidelijker, sneller en beschermd tegen een hele groep fouten. In deze les leer je wanneer je welke gebruikt, hoe je waarden handig uitpakt (unpacking), en hoe je met sets in één regel vergelijkt wat twee groepen gemeen hebben.

List versus tuple

boodschappen = ["melk", "brood", "kaas"]   # list: vierkante haken
punt = (52.09, 5.12)                        # tuple: ronde haken

boodschappen.append("eieren")
print(boodschappen)
punt[0] = 51.0
['melk', 'brood', 'kaas', 'eieren']
TypeError: 'tuple' object does not support item assignment

Een list is veranderlijk (mutable): je kunt toevoegen, verwijderen en vervangen. Een tuple is onveranderlijk (immutable): eenmaal gemaakt blijft hij zo.

Wanneer kies je wat?

list tuple
Typisch gebruik een verzameling van gelijksoortige dingen, waarvan het aantal kan veranderen een vaste groep waarden die samen één ding vormen
Voorbeeld alle bestellingen van vandaag een coördinaat (lat, lon), een RGB-kleur (255, 128, 0)
Veranderen ja nee
Als sleutel in een dict of element van een set nee ja (als de inhoud ook onveranderlijk is)

Een tuple met één element vraagt om een komma: (5,). Zonder komma is (5) gewoon het getal 5 tussen haakjes.

Een belangrijke valkuil met lijsten: een toekenning maakt geen kopie, maar een tweede naam voor dezelfde lijst.

a = [1, 2, 3]
b = a
b.append(4)
print(a)

c = a.copy()
c.append(5)
print(a, c)
[1, 2, 3, 4]
[1, 2, 3, 4] [1, 2, 3, 4, 5]

Bij een tuple kan dit niet misgaan, omdat niemand hem kan veranderen. Dat is een van de redenen om voor een tuple te kiezen.

Unpacking

Een reeks kun je in één keer uitpakken in losse variabelen:

punt = (52.09, 5.12)
lat, lon = punt
print(lat)

a, b = 1, 2
a, b = b, a          # wisselen zonder hulpvariabele
print(a, b)

eerste, *rest = [10, 20, 30, 40]
print(eerste, rest)

*begin, laatste = "abcd"
print(begin, laatste)
52.09
2 1
10 [20, 30, 40]
['a', 'b', 'c'] d

Met een * vang je "de rest" op in een lijst. Het aantal namen moet anders precies kloppen:

x, y = (1, 2, 3)
ValueError: too many values to unpack (expected 2)

Unpacking werkt ook in een for-lus, en dat zie je overal:

steden = [("Utrecht", 361_000), ("Zwolle", 132_000)]
for naam, inwoners in steden:
    print(f"{naam}: {inwoners:,}")

for index, stad in enumerate(["Gouda", "Delft"]):
    print(index, stad)
Utrecht: 361,000
Zwolle: 132,000
0 Gouda
1 Delft

enumerate en zip geven tuples terug, die je zo meteen uitpakt.

Sets

Een set is een verzameling zonder volgorde en zonder dubbele waarden:

bezoekers = ["sam", "noor", "sam", "ali", "noor"]
uniek = set(bezoekers)
print(len(bezoekers), len(uniek))
print("ali" in uniek)
5 3
True

De volgorde van een set is niet vast; print je hem, dan kan de volgorde anders zijn dan je verwacht. Wil je een gesorteerde lijst, gebruik dan sorted(uniek).

Opzoeken met in is bij een set heel snel, ook bij miljoenen elementen. Bij een lijst moet Python alle elementen langslopen. Moet je vaak controleren of iets erin zit, gebruik dan een set (meer hierover in les 34).

Verzamelingsbewerkingen

cursus_a = {"sam", "noor", "ali", "lisa"}
cursus_b = {"noor", "lisa", "tim"}

print(sorted(cursus_a & cursus_b))   # in beide (doorsnede)
print(sorted(cursus_a | cursus_b))   # in een van beide (vereniging)
print(sorted(cursus_a - cursus_b))   # alleen in a (verschil)
print(sorted(cursus_a ^ cursus_b))   # in precies één van beide
['lisa', 'noor']
['ali', 'lisa', 'noor', 'sam', 'tim']
['ali', 'sam']
['ali', 'sam', 'tim']

Een lege set maak je met set(). {} is een lege dictionary (les 6).

Elementen van een set moeten onveranderlijk zijn: getallen, strings en tuples mogen, lijsten niet (TypeError: unhashable type: 'list').

Uitgewerkt voorbeeld: wie is er nieuw?

Je hebt de ledenlijst van vorig jaar en van dit jaar, met dubbele invoer. Wie is er nieuw, wie is vertrokken? Voorspel de uitvoer.

vorig_jaar = ["Sam", "Noor", "Ali", "Noor", "Lisa"]
dit_jaar = ["Noor", "Lisa", "Tim", "Eva", "Tim"]

oud = set(vorig_jaar)
nieuw = set(dit_jaar)

print("Nieuw:", ", ".join(sorted(nieuw - oud)))
print("Vertrokken:", ", ".join(sorted(oud - nieuw)))
print("Gebleven:", len(oud & nieuw))
Nieuw: Eva, Tim
Vertrokken: Ali, Sam
Gebleven: 2

Met lijsten en lussen was dit een veel langer stuk code geweest.

Veelgemaakte fouten

  • b = a en denken dat je een kopie hebt. Gebruik a.copy() of list(a).
  • (5) in plaats van (5,) voor een tuple met één element.
  • {} gebruiken voor een lege set.
  • Rekenen op de volgorde van een set.

Oefenen

Oefening 1. Verwijder de dubbele waarden uit [3, 1, 3, 2, 1], maar behoud de volgorde waarin ze het eerst voorkwamen: [3, 1, 2].

Modelantwoord oefening 1
getallen = [3, 1, 3, 2, 1]
gezien = set()
uniek = []
for g in getallen:
    if g not in gezien:
        gezien.add(g)
        uniek.append(g)
print(uniek)
print(list(dict.fromkeys(getallen)))
[3, 1, 2]
[3, 1, 2]

set(getallen) alleen verliest de volgorde. De tweede regel is een bekende truc: de sleutels van een dictionary blijven in invoegvolgorde (les 6).

Oefening 2. Pak record = ("Noor", "Jansen", 34, "Utrecht") uit, zodat naam de voornaam bevat en woonplaats de laatste waarde, en de rest wordt genegeerd.

Modelantwoord oefening 2
record = ("Noor", "Jansen", 34, "Utrecht")
naam, *_, woonplaats = record
print(naam, woonplaats)
Noor Utrecht

_ is de gebruikelijke naam voor een waarde die je niet gebruikt.

Oefening 3. Waarom kun je een tuple (52.09, 5.12) als sleutel in een dictionary gebruiken, maar een lijst [52.09, 5.12] niet?

Modelantwoord oefening 3

Sleutels (en set-elementen) moeten hashable zijn: hun waarde mag niet veranderen, anders kan Python ze niet terugvinden. Een tuple is onveranderlijk, een lijst niet. Probeer je het toch, dan krijg je TypeError: unhashable type: 'list'.

Lab: deelnemerslijsten vergelijken

  1. In deelnemers.py staan drie lijsten met e-mailadressen van deelnemers aan drie workshops, met dubbele invoer en verschillen in hoofdletters.
  2. Schrijf normaliseer(lijst) die een set met adressen in kleine letters en zonder spaties teruggeeft.
  3. Schrijf alle_drie(a, b, c), precies_een(a, b, c) en nieuwe_deelnemers(oud, nieuw) met verzamelingsbewerkingen. Geef gesorteerde lijsten terug.
  4. Schrijf afstanden(punten), dat een lijst (naam, (x, y))-tuples krijgt en met unpacking voor elk punt de afstand tot (0, 0) berekent.
  5. Controleer met beetje check python-les5.

Samenvatting

  • list: veranderlijke reeks; tuple: onveranderlijke vaste groep; set: unieke waarden zonder volgorde.
  • b = a maakt geen kopie van een lijst; a.copy() wel.
  • Unpacking: x, y = punt, a, b = b, a, eerste, *rest = lijst, ook in for-lussen.
  • Sets zijn snel voor in en hebben &, |, - en ^.

In les 6 gebruik je dictionaries, ook genest en met Counter en defaultdict.

Lab

Gemeenschappelijke vakken van twee klassen vinden

💻 Dit lab doe je op je eigen computer met Docker. Start de lab-container met (geeft de opdracht een eigen startcommando, gebruik dan dat):

$ docker run -it --rm registry.wizg.it/webhosting/beetje-labs:python-les5

Volg daarna de stappen van de opdracht hierboven. Nog geen Docker? Zie les 14 van Een beetje Linux & Docker. Voor dit lab is er geen live lab in de browser.

Klaar? Controleer je werk met:

$ beetje check python-les5

📝 Quiz: lijsten, tuples en sets

6 vragen · 70% nodig om te slagen

1. Wat gebeurt er bij `punt = (1, 2)` gevolgd door `punt[0] = 5`?
2. Na `a = [1, 2]`, `b = a` en `b.append(3)`, wat is a?
3. Wat is `rest` na `eerste, *rest = [1, 2, 3]`?
4. Wat geeft `{1, 2, 3} & {2, 3, 4}`?
5. Hoe maak je een lege set?
6. Welke datastructuur kies je voor een vaste combinatie van breedte- en lengtegraad?