← Python/Idiomatisch en performance
Les 8 van 8
MODULE 13 · 08◷ 30 min

Een prijsrapport verbeteren en het gedrag bewijzen

Na deze les kun je
  • Herhaalde zoekrondes door een prijsindex vervangen.
  • Uitkomsten, invoercontracten en foutpaden bij een verbetering behouden.
  • Werkelijke zoekhandelingen tellen en kosten van indexbouw meenemen.

Je rapport zoekt voor iedere bestelling opnieuw door de hele prijslijst. Bij enkele regels merk je weinig, maar het zoekwerk groeit mee met zowel de bestellingen als de catalogus. Je wilt de herhaalde zoekronde verminderen en tegelijk bewijzen dat het rapport hetzelfde blijft betekenen.

Eerst het gedrag vastzetten

Een regressie is gedrag dat na een verandering onbedoeld verslechtert. Schrijf daarom eerst op wat behouden moet blijven. In deze les mag een code vaker besteld zijn, is een onbekende code een fout en mag een dubbele cataloguscode niet stil een prijs overschrijven.

Een lege bestelling heeft totaal nul. De invoer blijft ongewijzigd. Door deze afspraken vooraf te testen, voorkom je dat een snelle versie vooral minder werk doet doordat zij gegevens vergeet.

Zoekwerk eenmaal voorbereiden

Een index koppelt een zoeksleutel rechtstreeks aan de bijbehorende waarde. Hier wordt dat een dictionary van artikelcode naar prijs. Je loopt eenmaal door de catalogus om die index op te bouwen en gebruikt daarna een dictionarylookup per bestelling.

Een dictionarylookup heeft bij gebruikelijke sleutels gemiddeld een constante kost, maar dat is geen absolute garantie voor iedere denkbare invoer. De index kost daarnaast geheugen en opbouwtijd. Voor één kleine zoekvraag kan een eenvoudige lus daarom een prima keuze blijven.

Indexbouw en aanvragen afzonderlijk tellen
rows = [("p", 3), ("q", 5), ("r", 7), ("s", 9)]
codes = ["r", "s"] * 3
scans = 0
slow_total = 0
for code in codes:
    for tag, price in rows:
        scans += 1
        if tag == code:
            slow_total += price
            break
print(slow_total)
print(scans)
index, built = {}, 0
for tag, price in rows:
    built += 1
    index[tag] = price
reads = 0
fast_total = 0
for code in codes:
    reads += 1
    fast_total += index[code]
print(fast_total)
print(built)
print(reads)
Verwachte uitvoer
48
21
48
4
6

Dit voorbeeld voert beide versies echt uit en telt de uitgevoerde zoekstappen. De indexversie telt ook hoeveel catalogusregels zijn voorbereid. De twee totalen zijn gelijk; de kosten zijn in verschillende onderdelen uitgesplitst.

Een verbetering meten zonder de uitkomst te raden

Een tijdmeting met timeit kan later laten zien wat deze keuze op jouw machine oplevert. Kies dan dezelfde gegevens, herhalingen en scope. Als je de index maar één keer gebruikt, hoort het bouwen bij die vergelijking; bij hergebruik kun je opbouw en aanvragen apart rapporteren.

De oefencontrole gebruikt daarnaast stringcodes met een teller op hun gelijkheidsvergelijkingen en behoudt het gewone stringhashcontract. Dat onderscheidt concrete herhaalde-zoekvarianten op deze invoer. Het bewijst niet dat ieder denkbaar kwadratisch algoritme wordt uitgesloten.

Een teller voor zoekhandelingen beantwoordt een andere vraag dan een timer. Hij laat zien hoeveel van een afgesproken soort werk gebeurde, zonder te claimen hoeveel seconden dat kostte. Beide zijn nuttig als je hun betekenis erbij vertelt.

Herhalingen efficiënt bijhouden

Voor het rapport wil je ook weten hoe vaak iedere code voorkwam. Loop eenmaal door de codes en verhoog in een dictionary hun teller. Voor iedere code opnieuw de hele lijst doorzoeken werkt tegen hetzelfde probleem aan als de oorspronkelijke prijszoeker.

De codes kunnen een iterator zijn, die je maar eenmaal kunt verbruiken. Verzamel daarom de aantallen en gebruik die samenvatting voor het eindtotaal. Een code met aantal drie draagt drie keer zijn prijs bij.

De iterator hoeft geen tweede ronde te maken
def source():
    for code in ["B", "A", "B"]:
        print(f"lees {code}")
        yield code

items = source()
counts = {}
for code in items:
    counts[code] = counts.get(code, 0) + 1
print(sorted(counts.items()))
print(list(items))
Verwachte uitvoer
lees B
lees A
lees B
[('A', 1), ('B', 2)]
[]

Het tweede voorbeeld gebruikt een eigen generator en laat zien dat iedere bronwaarde eenmaal wordt aangeboden. De uitkomst bevat ook herhalingen, terwijl de bron daarna uitgeput is. Een tweede lus over dezelfde iterator zou de gegevens niet terughalen.

Veelgemaakte fout: een ontbrekende prijs wordt gratis

Met index.get(code, 0) verdwijnt een onbekende code stil uit de rekening. Dat is snel en geeft een getal terug, maar het verandert het foutcontract. Gebruik een echte lookup die de bestaande KeyError doorgeeft.

Ook een dictionarycomprehension kan dubbele cataloguscodes ongemerkt overschrijven. Controleer tijdens de indexbouw of de code er al staat. Zelfs twee gelijke prijzen moeten volgens deze oefening als dubbel worden geweigerd.

AAN DE SLAG 0/4
1

Een index met expliciete dubbele-codecontrole

Vul build_price_index(rows) in: doorloop paren (code, prijs) eenmaal en geef een nieuwe dict. Een herhaalde code geeft ValueError("artikelcode dubbel"), ook als de prijs gelijk is. Wijzig de bron niet.

2

Eén lookup per bestelling

Vul indexed_total(codes, index) in: tel index[code] voor iedere code op, ook bij herhalingen. Gebruik precies één lookup per gelezen code, wijzig de index niet en geef een oorspronkelijke KeyError ongewijzigd door.

3

Aantallen tijdens één bronronde opbouwen

Vul count_codes(codes) in: geef een dict met het aantal voorkomens per code. Werk de teller tijdens één doorgang bij; vermijd herhaalde volledige zoekrondes en verander de bron niet.

4

Een rapport samenstellen zonder bronherhaling

Vul make_report(codes, rows) in: bouw eenmaal de prijsindex en eenmaal de telling. Geef (totaalbedrag, alfabetisch gesorteerde lijst (code, aantal)-tuples). Behoud dubbele-code- en onbekende-codefouten; beide bronnen mogen maar eenmaal worden doorlopen.

Samenvatting

  • Leg vóór verbeteren vast welke resultaten, mutaties en fouten behouden blijven.
  • Een index ruilt opbouw en geheugen voor minder herhaalde zoekrondes.
  • Tel en meet echte handelingen; een gelijke uitkomst alleen bewijst geen kostenbesparing.

De volgende module brengt de geleerde onderdelen samen in een groter eindproject met duidelijke regressietests.

report_tools.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.