Beetje Coderen

Les 6

Dictionaries in de praktijk

Dictionaries nesten, tellen en groeperen.

⏱ 45–60 minuten leestijd + oefenen

🎯 Na deze les kun je

  • geneste dictionaries lezen en aanpassen
  • tellen met Counter en groeperen met defaultdict
  • dictionaries sorteren op waarde

Een dictionary koppelt sleutels aan waarden: een productcode aan een prijs, een gebruikersnaam aan een profiel, een woord aan het aantal keer dat het voorkomt. Je kent de basis al uit Programmeerconcepten. In de praktijk krijg je te maken met dictionaries in dictionaries (bijvoorbeeld JSON uit een web-API, les 15 en 25), en met tellen en groeperen. Daarvoor heeft Python twee handige hulpmiddelen: Counter en defaultdict.

Snel herhalen

prijzen = {"appel": 0.50, "peer": 0.65}
prijzen["kiwi"] = 0.30              # toevoegen of vervangen
print(prijzen["appel"])
print(prijzen.get("mango"))         # None als de sleutel ontbreekt
print(prijzen.get("mango", 0.0))    # of een eigen standaardwaarde
print("peer" in prijzen)

for product, prijs in prijzen.items():
    print(f"{product}: {prijs:.2f}")
0.5
None
0.0
True
appel: 0.50
peer: 0.65
kiwi: 0.30

prijzen["mango"] zou een KeyError geven. Gebruik get als een sleutel mag ontbreken, en [] als hij er moet zijn (dan wil je juist een fout als dat niet zo is).

Een dictionary onthoudt de volgorde waarin sleutels zijn toegevoegd.

Geneste structuren

Data van buiten heeft vaak meerdere niveaus:

bestelling = {
    "id": 1042,
    "klant": {"naam": "Noor Jansen", "plaats": "Utrecht"},
    "regels": [
        {"product": "appel", "aantal": 6, "prijs": 0.50},
        {"product": "kaas", "aantal": 1, "prijs": 7.95},
    ],
}

print(bestelling["klant"]["naam"])
print(bestelling["regels"][1]["product"])

totaal = 0
for regel in bestelling["regels"]:
    totaal += regel["aantal"] * regel["prijs"]
print(f"Totaal: {totaal:.2f}")
Noor Jansen
kaas
Totaal: 10.95

Lees een pad als bestelling["regels"][1]["product"] van links naar rechts: de dictionary bestelling, daarin de lijst regels, daarin het element met index 1 (een dictionary), daarin de sleutel product.

Is een tussenliggend niveau optioneel, dan kun je get aaneenschakelen:

bestelling = {"id": 7, "klant": {"naam": "Ali"}}
print(bestelling.get("klant", {}).get("plaats", "onbekend"))
onbekend

Tellen met Counter

Tellen hoe vaak iets voorkomt is zo gewoon dat Python er een eigen type voor heeft:

from collections import Counter

tekst = "de kat zat op de mat en de kat sliep"
telling = Counter(tekst.split())
print(telling["de"])
print(telling["hond"])
print(telling.most_common(2))
3
0
[('de', 3), ('kat', 2)]

Een Counter is een dictionary die voor onbekende sleutels 0 teruggeeft in plaats van een KeyError. most_common(n) geeft de n vaakst voorkomende waarden. Je kunt hem ook bijwerken:

from collections import Counter

statussen = Counter()
for code in [200, 404, 200, 500, 200, 404]:
    statussen[code] += 1
print(statussen)
Counter({200: 3, 404: 2, 500: 1})

Groeperen met defaultdict

Wil je dingen in groepen verdelen, dan moet je bij een gewone dictionary steeds controleren of de groep al bestaat. defaultdict doet dat voor je:

from collections import defaultdict

leerlingen = [("Sam", "4A"), ("Noor", "4B"), ("Ali", "4A"), ("Lisa", "4B"), ("Tim", "4A")]

per_klas = defaultdict(list)
for naam, klas in leerlingen:
    per_klas[klas].append(naam)

print(dict(per_klas))
{'4A': ['Sam', 'Ali', 'Tim'], '4B': ['Noor', 'Lisa']}

defaultdict(list) maakt automatisch een lege lijst als je een sleutel gebruikt die er nog niet is. Andere varianten: defaultdict(int) begint bij 0 (tellen), defaultdict(set) bij een lege set.

Zonder defaultdict schrijf je hetzelfde met setdefault:

per_klas = {}
for naam, klas in [("Sam", "4A"), ("Ali", "4A")]:
    per_klas.setdefault(klas, []).append(naam)
print(per_klas)
{'4A': ['Sam', 'Ali']}

Een defaultdict maakt ook een sleutel aan als je hem alleen leest: per_klas["5C"] voegt "5C": [] toe. Wil je controleren of een sleutel bestaat, gebruik dan "5C" in per_klas.

Uitgewerkt voorbeeld: een logbestand samenvatten

Per IP-adres willen we weten hoeveel verzoeken er waren en welke paden het opvroeg. Voorspel de uitvoer.

from collections import Counter, defaultdict

log = """\
10.0.0.1 GET /index.html 200
10.0.0.2 GET /contact 404
10.0.0.1 GET /over 200
10.0.0.3 POST /login 500
10.0.0.1 GET /index.html 200
"""

per_ip = Counter()
paden = defaultdict(set)
for regel in log.splitlines():
    ip, methode, pad, status = regel.split()
    per_ip[ip] += 1
    paden[ip].add(pad)

for ip, aantal in per_ip.most_common():
    print(f"{ip:<10} {aantal}  {sorted(paden[ip])}")
10.0.0.1   3  ['/index.html', '/over']
10.0.0.2   1  ['/contact']
10.0.0.3   1  ['/login']

paden is een defaultdict(set), zodat een pad dat twee keer voorkomt maar één keer telt.

Veelgemaakte fouten

  • KeyError bij een sleutel die soms ontbreekt; gebruik get.
  • Een dictionary veranderen terwijl je eroverheen loopt: RuntimeError: dictionary changed size during iteration. Loop over een kopie: for k in list(d):.
  • Een lijst als sleutel (les 5): TypeError: unhashable type: 'list'.
  • Met een defaultdict per ongeluk lege sleutels aanmaken door ze op te vragen.

Oefenen

Oefening 1. Tel hoe vaak elke letter voorkomt in "mississippi" en print de drie meest voorkomende.

Modelantwoord oefening 1
from collections import Counter
print(Counter("mississippi").most_common(3))
[('i', 4), ('s', 4), ('p', 2)]

Een string is een reeks tekens, dus Counter telt per letter.

Oefening 2. Groepeer de woorden ["appel", "ananas", "banaan", "bes", "citroen"] op hun eerste letter.

Modelantwoord oefening 2
from collections import defaultdict

groepen = defaultdict(list)
for woord in ["appel", "ananas", "banaan", "bes", "citroen"]:
    groepen[woord[0]].append(woord)
print(dict(groepen))
{'a': ['appel', 'ananas'], 'b': ['banaan', 'bes'], 'c': ['citroen']}

Oefening 3. Gegeven de bestelling uit het voorbeeld hierboven: schrijf een expressie die de plaats van de klant geeft, en "onbekend" als klant of plaats ontbreekt.

Modelantwoord oefening 3
bestelling = {"id": 1042, "klant": {"naam": "Noor Jansen"}}
print(bestelling.get("klant", {}).get("plaats", "onbekend"))
onbekend

Lab: woordfrequenties en een index

  1. In boek.txt staat een kort verhaal.
  2. Schrijf woordtelling(tekst) die een Counter teruggeeft van alle woorden in kleine letters, zonder leestekens (.,!?;:"').
  3. Schrijf index(tekst) die per woord een gesorteerde lijst van regelnummers (vanaf 1) teruggeeft waarop het voorkomt. Gebruik defaultdict(set).
  4. Schrijf top(tekst, n) die de n meest voorkomende woorden van minstens 4 letters teruggeeft.
  5. Controleer met beetje check python-les6.

Samenvatting

  • d[k] voor sleutels die er moeten zijn, d.get(k, standaard) voor sleutels die mogen ontbreken; items() om over sleutels en waarden te lopen.
  • Geneste data lees je stap voor stap: data["regels"][0]["product"].
  • Counter telt en heeft most_common.
  • defaultdict(list) en defaultdict(set) groeperen zonder steeds te controleren of de groep al bestaat.

In les 7 schrijf je lijsten, sets en dictionaries in één regel met comprehensions.

Lab

Woordfrequenties in een tekst tellen

💻 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-les6

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-les6

📝 Quiz: dictionaries in de praktijk

5 vragen · 70% nodig om te slagen

1. Wat geeft `{"a": 1}.get("b", 0)`?
2. Wat geeft `Counter("aab")["z"]`?
3. Wat doet `defaultdict(list)` bij een onbekende sleutel?
4. Hoe lees je de naam in `data = {"klant": {"naam": "Sam"}}`?
5. Welke methode van Counter geeft de vaakst voorkomende waarden?

Dit was de laatste gratis les

Je hebt les 1–6 van Een beetje Python gratis gevolgd. Wil je verder? Met één aankoop (€ 24,00, eenmalig) krijg je de overige 33 lessen, de live labs en het certificaat. Geen abonnement.

Cursus kopen en verdergaan