3.2 Ordbøker

Ordbøker (`dict`) som oppslagstabell, **teller** og **grupperingsstruktur** — den nest viktigste datastrukturen, og datamodellen bak «en-til-mange» i klassemodelleringen.

50 min
8 oppgaver
Ordbøker
Din fremgang i kapitlet
0 / 8 oppgaver

Forkunnskaper — sist du var her

Kap. 3.1 — Lister og, gjennom den,
kap. 1.3 — Løkker og
kap. 2.1 — Funksjoner.

De tre tingene du bruker mest her:

- bygg-en-ny-liste-mønsteret: tom liste før løkka, append inne i den,
return etter,
- len(...), som virker både på lister og tekster,
- finn beste med en startverdi hentet fra dataene, ikke fra hva du tror om
dem.

Kort oppfrisket:

tall = [4, 11, 7]
store = []
for e in tall:
    if e > 5:
        store.append(e)
print(store, len(store))

Utskrift:

[11, 7] 2

I dette kapitlet får samlingen navn på plassene i stedet for nummer. Det er hele forskjellen mellom en liste og en ordbok — og den forskjellen avgjør hvilke spørsmål du kan svare på uten å lete.

Løkke 1 — Hva en ordbok er, og hvordan du slår opp (~12 min)

En resirkuleringsstasjon veier inn kartong, glass og metall. Med en liste kunne du lagret de tre tallene — men da måtte du huske at kartong står først og metall sist, og hver gang noen la til en ny avfallstype, ville rekkefølgen bli en kilde til feil.

Det du egentlig vil, er å slå opp på navnet: «hvor mange kilo glass?». Det er akkurat det en ordbok gjør.

Ordbok

En samling par av en nøkkel og en verdi, der du slår opp verdien ved å oppgi nøkkelen.

Du skriver den med krøllparenteser, kolon mellom nøkkel og verdi, og komma mellom parene:

levert = {"kartong": 12, "glass": 7, "metall": 3}
print(levert)
print(levert["glass"])

Utskrift:

{'kartong': 12, 'glass': 7, 'metall': 3}
7

Sammenlign med lista: der er plassen et tall du må holde styr på selv, her er plassen et navn du velger. Det er hele forskjellen, og den gjør oppslag enkle: du trenger ikke lete gjennom samlingen for å finne det du er ute etter.

Ordboka heter dict i Python, etter det engelske ordet for ordbok. Du vil se det navnet i feilmeldinger.

`{nøkkel: verdi}` — ordboklitteral og tom ordbok

En ordbok skrevet rett inn i koden. Den kan være tom:

tom = {}
antall = {"kartong": 12, "glass": 7}
print(tom, len(tom))
print(antall, len(antall))

Utskrift:

{} 0
{'kartong': 12, 'glass': 7} 2

len(...) gir antall par, ikke antall tegn eller verdier.

Den tomme ordboka {} er startpunktet i alle tre idiomene du skal lære her — akkurat som den tomme lista var det i forrige kapittel. Merk at det er krøllparenteser, ikke hakeparenteser: [] er en tom liste, {} er en tom ordbok.

Nøkkel og verdi

De to halvdelene av et par.

Nøkkelen er det du slår opp på — som regel en tekst, men den kan også være et
tall. Verdien er det du får igjen, og den kan være hva som helst: et tall, en tekst, en liste, eller senere i boka et helt objekt.

I {"kartong": 12} er "kartong" nøkkelen og 12 verdien. Rekkefølgen i skrivemåten er alltid nøkkel først, så kolon, så verdi.

Én regel skiller de to: nøklene er unike, mens verdiene godt kan gjenta seg.
To varer kan ha tallet 12, men det finnes bare én "kartong".

Oppslag `ordbok[nøkkel]`

Henter verdien som hører til nøkkelen. Skrivemåten er den samme hakeparentesen som i en liste — men innholdet er en nøkkel, ikke en plass:

levert = {"kartong": 12, "glass": 7, "metall": 3}
print(levert["glass"])
print(levert["kartong"] + levert["metall"])

Utskrift:

7
15

Resultatet er en helt vanlig verdi. Du kan regne med den, sammenligne den eller sende den videre til en funksjon.

Rekkefølgen betyr ingenting for oppslaget. levert["metall"] finner det
samme uansett om metall står først eller sist. Det er derfor du slipper å telle.

`ordbok[nøkkel] = verdi`

Setter inn et nytt par — eller overskriver verdien hvis nøkkelen finnes fra før:

levert = {"kartong": 12}
levert["glass"] = 7 levert["kartong"] = 20 print(levert)

Utskrift:

{'kartong': 20, 'glass': 7}

Legg merke til at de to siste linjene ser helt like ut, men gjør to ulike ting.
Den første oppretter nøkkelen "glass", som ikke fantes. Den andre
erstatter verdien til "kartong", og det gamle tallet 12 er borte for godt.

Dette er en viktig forskjell fra lista: der måtte plassen finnes fra før for at
liste[i] = v skulle virke. I en ordbok lages nøkkelen ved behov.

`nøkkel in ordbok`

Svarer True eller False på om nøkkelen finnes:

levert = {"kartong": 12, "glass": 7}
print("glass" in levert)
print("plast" in levert)
print(12 in levert)

Utskrift:

True
False
False

Siste linje er fellen. Testen ser bare på nøklene, aldri på verdiene — og 12 er en verdi her, ikke en nøkkel.

in er selve sikkerhetsnettet foran et oppslag: slår du opp en nøkkel som ikke finnes, stopper programmet. Vanen som lønner seg er å skrive if k in d: før
d[k] hver gang du ikke er helt sikker.

✏️Eksempel 1: En dags innveiing på resirkuleringsstasjonen

Stasjonen starter dagen med 12 kilo kartong, 7 kilo glass og 3 kilo metall.
I løpet av dagen kommer det inn 9 kilo plast, som er en ny avfallstype, og 5 kilo glass i tillegg til det som allerede var registrert. Skriv ut hele oversikten, hvor mange typer som er registrert, og hva som er levert av glass
— og svar pent på et oppslag av en type som ikke finnes.

Alt skjer i den samme ordboka:

levert = {"kartong": 12, "glass": 7, "metall": 3}
levert["plast"] = 9 levert["glass"] = levert["glass"] + 5 print(levert)
print(len(levert))
print(levert["glass"], "kilo glass")
if "papir" in levert:
    print(levert["papir"])
else:
    print("ingen papir levert")

Utskrift:

{'kartong': 12, 'glass': 12, 'metall': 3, 'plast': 9}
4
12 kilo glass ingen papir levert

Fire ting er verdt å merke seg.

De to tilordningene ser like ut, men gjør ulike ting. levert["plast"] = 9
oppretter en ny nøkkel; levert["glass"] = levert["glass"] + 5 leser den gamle verdien først og skriver den nye tilbake. Det siste er selve mønsteret bak alle tellere i ordbok.

Den nye nøkkelen havner bakerst. Python husker rekkefølgen parene ble satt
inn i, og "plast" kom sist.

len teller par, ikke kilo. Svaret er 4 fordi det er fire avfallstyper.

Oppslaget av "papir" er beskyttet av en if. Uten den ville programmet
stoppet med KeyError, og alle linjer under ville aldri kjørt — også de riktige.

📝Oppgave 1

(Innstegsoppgave — ren gjengivelse.) En brettspillklubb registrerer hvor mange eksemplarer de har av hvert spill:

lager = {"sjakk": 4, "yatzy": 2, "domino": 6}

a) Hva gir len(lager)?

b) Hva gir lager["yatzy"]?

c) Hva gir "domino" in lager?

d) Hva gir 6 in lager?

e) Hva skjer hvis du skriver lager["ludo"]?

📝Oppgave 2
Eksamensnivå, sjanger A

Hva skriver programmet ut? Skriv utskriften nøyaktig, med mellomrom og linjeskift slik terminalen viser den.

pris = {"kaffe": 25, "te": 20}
pris["kaffe"] = pris["kaffe"] + 5 pris["kakao"] = 30 print(pris)
print(len(pris), pris["kaffe"])
print("te" in pris, "juice" in pris)

Løkke 2 — Å gå gjennom en ordbok, og teller-idiomet (~14 min)

Nå kommer det viktigste enkeltmønsteret i hele Oppgave 3. Men først må du vite hva en løkke over en ordbok egentlig gir deg — og svaret overrasker mange.

`for nøkkel in ordbok:`

Går gjennom nøklene, ikke verdiene. Verdien henter du selv med et oppslag:

levert = {"kartong": 12, "glass": 7}
for vare in levert:
    print(vare, levert[vare])

Utskrift:

kartong 12 glass 7

Dette er den ene tingen alle bommer på første gang: løkkevariabelen er
nøkkelen. Vil du ha verdien, skriver du levert[vare] inne i løkka.

Rekkefølgen er den parene ble satt inn i. Det er ikke noe du skal bygge en løsning på, men det er greit å vite når du sporer en utskrift.

`len(ordbok)` og summering
len gir antall par. Skal du ha summen av verdiene, må du løpe gjennom og akkumulere selv:

levert = {"kartong": 12, "glass": 7, "metall": 3}
print(len(levert))
total = 0 for vare in levert:
    total = total + levert[vare]
print(total)

Utskrift:

3
22

Legg merke til at akkumulatormønsteret er helt uendret fra
kap. 1.3: startverdi før løkka, oppdatering inne i den, bruk etter. Det eneste nye er at verdien hentes med et oppslag.

Det er verdt å si høyt: len teller par, ikke kilo. Å blande antall nøkler med summen av verdiene er en klassisk lesefeil i en sporingsoppgave.

📜Teller-idiomet — ordbok som forekomsttabell

Bokas femte kodemønster, og det som gir flest poeng i Oppgave 3. Det brukes hver gang oppgaven sier «tell hvor mange ganger …».

Oppskriften har tre faste steg:

1. Før løkka: lag en tom ordbok, antall = {}.
2. Inne i løkka, først: finnes ikke nøkkelen ennå, opprett den med verdien
0 — if vare not in antall: antall[vare] = 0.
3. Inne i løkka, deretter: øk med én —
antall[vare] = antall[vare] + 1.

leveranser = ["glass", "kartong", "glass", "plast", "glass"]
antall = {}
for vare in leveranser:
    if vare not in antall:
        antall[vare] = 0 antall[vare] = antall[vare] + 1 print(antall)

Utskrift:

{'glass': 3, 'kartong': 1, 'plast': 1}

Steg 2 er ikke valgfritt. Uten det ville første antall[vare] + 1 slå opp en
nøkkel som ikke finnes, og programmet ville stoppet med KeyError — feilkode
#14. De to linjene hører sammen som ett grep, og du bør skrive dem som ett.

Legg merke til at if-en ikke har noen else. Etter if-blokken er nøkkelen garantert på plass, uansett om den nettopp ble opprettet eller fantes fra før, og da kan økningen skje på én linje for begge tilfeller.

✏️Eksempel 2: Tell forekomster, som en gjenbrukbar funksjon

Skriv tell_forekomster(elementer) som tar imot en liste og returnerer en ordbok fra hvert element til hvor mange ganger det finnes i lista. Vis også hva funksjonen gjør med en tom liste.

Idiomet flyttes rett inn i en funksjon:

def tell_forekomster(elementer):
    antall = {}
    for e in elementer:
        if e not in antall:
            antall[e] = 0 antall[e] = antall[e] + 1 return antall

print(tell_forekomster(["buss", "trikk", "buss"]))
print(tell_forekomster([]))

Utskrift:

{'buss': 2, 'trikk': 1}
{}

Slik ville sensor sett på det: fire ting gir uttelling hver for seg. Den tomme
ordboka før løkka, løkka over lista, opprettelsen av nøkkelen med 0, og
return etter løkka. Har du de tre første riktig, men glemt return, er strukturen på plass og trekket lite — men funksjonen gir None, og alle deloppgaver som bygger videre, faller sammen.

Den tomme lista gir en tom ordbok. Løkka kjører null ganger, og antall
returneres slik den ble laget. Det er et rimelig svar, og du fikk det gratis av mønsteret — men det er verdt å sjekke bevisst, jf. feilkode #6 — kant-tilfellene, som tom samling og «ikke funnet».

Funksjonen er verdt å legge på minnet. Den er halve svaret på ethvert «hvilken forekommer flest ganger?»-spørsmål, og andre halvdel kommer i løkke 4.

📝Oppgave 3
Eksamensnivå, sjanger A

Hva skriver programmet ut?

poeng = {"ada": 1, "bo": 2, "cato": 3}
for navn in poeng:
    poeng[navn] = poeng[navn] * 2 print(poeng)
total = 0 for navn in poeng:
    total = total + poeng[navn]
print(total)

📝Oppgave 4
Eksamensnivå, sjanger D

Skriv funksjonen
tell_tegn(tekst) som returnerer en ordbok fra hvert tegn i teksten til hvor mange ganger tegnet forekommer.

a) Skriv funksjonen med teller-idiomet.

b) Hva returnerer tell_tegn("")?

c) Hva ville gått galt hvis du droppet linja if tegn not in antall:?

Løkke 3 — Gruppering: en liste som verdi (~12 min)

Teller-idiomet svarer på «hvor mange». Det neste idiomet svarer på «hvilke»: du vil ikke bare vite at tre turer var korte, du vil ha turene.

Grepet er å la verdien være en liste. Da holder ordboka en hel gruppe per nøkkel, og det er nøyaktig den strukturen «en-til-mange» har i den store klassemodelleringen.

📜Grupperings-idiomet

Bokas sjette kodemønster. Det er teller-idiomet med to endringer: startverdien er en tom liste i stedet for 0, og oppdateringen er append i stedet for
+ 1.

1. Før løkka: grupper = {}.
2. Inne i løkka, først: if merkelapp not in grupper: grupper[merkelapp] = [].
3. Inne i løkka, deretter: grupper[merkelapp].append(verdien).

turer = [12, 45, 8, 30, 60]
grupper = {}
for minutter in turer:
    if minutter < 15:
        merkelapp = "kort" elif minutter < 45:
        merkelapp = "middels" else:
        merkelapp = "lang" if merkelapp not in grupper:
        grupper[merkelapp] = []
    grupper[merkelapp].append(minutter)
print(grupper)

Utskrift:

{'kort': [12, 8], 'lang': [45, 60], 'middels': [30]}

Resultatet er en ordbok der hver verdi er en liste. Formen {nøkkel: [liste]} er verdt å kjenne igjen på synet — den dukker opp både i Oppgave 3, i Oppgave 5 og som instansvariabel i den store modelleringen.

To detaljer. Nøkkelrekkefølgen er den gruppene ble opprettet i: kort kom først (12 minutter), lang som nummer to (45 minutter), og middels sist (30 minutter). Og grupper[merkelapp].append(...) gjør to ting på én linje — først slår den opp lista, så legger den til i den. Nettopp derfor må lista finnes fra før, og det er hva steg 2 sørger for.

Liste som verdi i en ordbok

En verdi i en ordbok kan være hva som helst — også en liste.

grupper = {"kort": [12, 8, 3], "lang": [45]}
print(grupper["kort"])
print(grupper["kort"][1])
print(len(grupper), len(grupper["kort"]))

Utskrift:

[12, 8, 3]
8
2 3

Legg merke til den doble hakeparentesen på linje 2: grupper["kort"] gir lista, og [1] henter det andre elementet i den. Du leser slike uttrykk fra venstre, ett steg om gangen.

Og merk de to len-ene på siste linje. Den ene teller grupper og gir 2, den andre teller elementer i én gruppe og gir 3. De svarer på helt ulike spørsmål, og å blande dem er en klassisk lesefeil.

✏️Eksempel 3: Gruppering som funksjon

Skriv grupper_etter_lengde(ordene) som tar imot en liste med tekster og returnerer en ordbok fra ordlengde til lista over ord med den lengden. Vis også hva funksjonen gjør med en tom liste.

Nøkkelen er her et tall, ikke en tekst — det er helt lovlig:

def grupper_etter_lengde(ordene):
    grupper = {}
    for tekst in ordene:
        n = len(tekst)
        if n not in grupper:
            grupper[n] = []
        grupper[n].append(tekst)
    return grupper

print(grupper_etter_lengde(["buss", "tog", "trikk", "fly"]))
print(grupper_etter_lengde([]))

Utskrift:

{4: ['buss'], 3: ['tog', 'fly'], 5: ['trikk']}
{}

Slik ville sensor sett på det: den tomme ordboka før løkka, opprettelsen av
den indre lista, append på riktig nøkkel og return etter løkka gir uttelling hver for seg. Glemmer du linja som oppretter den indre lista, stopper programmet med KeyError — men ideen er riktig, og trekket er lite.

Legg merke til at nøklene skrives uten anførselstegn i utskriften. Det er
fordi de er heltall. Er nøkkelen en tekst, får den enkle anførselstegn; er den et tall, får den ikke. Den forskjellen er verdt poeng i et auto-rettet felt.

Og merk rekkefølgen: 4 først, fordi buss var det første ordet. Ordboka
sorterer ikke.

📝Oppgave 5
Eksamensnivå, sjanger D

En kantine registrerer antall solgte porsjoner per dag i en liste med heltall. Skriv funksjonen del_i_to(tallene) som returnerer en ordbok med nøklene "partall" og "oddetall", der hver verdi er lista over tallene i den gruppen.

a) Skriv funksjonen med grupperings-idiomet.

b) Hva returnerer del_i_to([2, 4]), og hvorfor er ikke begge nøklene med?

c) Hva måtte du gjort annerledes hvis begge nøklene alltid skulle finnes?

📝Oppgave 6
Eksamensnivå, sjanger A

Hva skriver programmet ut? Skriv utskriften nøyaktig, med mellomrom og linjeskift slik terminalen viser den.

d = {}
for tall in [3, 1, 3, 2, 1, 3]:
    if tall not in d:
        d[tall] = 0 d[tall] = d[tall] + 1 print(d)
print(len(d), d[3])

Løkke 4 — Finn beste: nøkkelen med størst verdi (~12 min)

Teller-idiomet gir deg en ordbok. Men eksamensoppgaven spør sjelden om ordboka
— den spør om hvilken som forekom flest ganger. Det er andre halvdel, og den er et eget mønster.

📜Finn-beste-løkka over en ordbok

Bokas sjuende kodemønster. Det brukes hver gang oppgaven sier «hvilken … flest»,
«den største», «den travleste».

Oppskriften har tre faste steg, og det avgjørende er at du holder styr på to
ting samtidig:

1. Før løkka: beste = None og beste_antall = 0.
2. Inne i løkka: er verdien større enn den beste hittil, oppdater begge.
3. Etter løkka: return beste.

antall = {"glass": 3, "kartong": 7, "plast": 5}
beste = None beste_antall = 0 for vare in antall:
    if antall[vare] > beste_antall:
        beste = vare beste_antall = antall[vare]
print(beste, beste_antall)

Utskrift:

kartong 7

Å oppdatere bare den ene er den vanligste feilen. Glemmer du
beste_antall = antall[vare], sammenligner du for alltid mot 0, og svaret blir den siste nøkkelen med positiv verdi i stedet for den største. Skriv de to linjene som ett grep.

Startverdien None betyr «ingen funnet ennå». Er ordboka tom, kjører løkka
null ganger og None returneres — et ærlig svar på et spørsmål som ikke har noe. Startverdien beste_antall = 0 er trygg så lenge alle antall er positive, som de er i en telling. Kan verdiene være negative, må du i stedet ta den første nøkkelen som utgangspunkt, slik du gjorde med lister i
kap. 3.1.

Ved likhet vinner den første, fordi testen er streng (>). Skriver du >=,
vinner den siste. Les oppgaveteksten: sier den noe om uavgjort?

✏️Eksempel 4: Full Oppgave 3-oppgave — hvilken forekommer flest ganger?

En frivilligsentral fører en liste over hvilke oppgaver de frivillige har meldt seg til. Skriv flest_av(elementer) som returnerer det elementet som forekommer flest ganger. Er lista tom, skal funksjonen returnere None. Bruk de to idiomene du nå kan, og la dem være hver sin funksjon.

Del problemet i to: først tell, så finn beste.

def tell_forekomster(elementer):
    antall = {}
    for e in elementer:
        if e not in antall:
            antall[e] = 0 antall[e] = antall[e] + 1 return antall

def flest_av(elementer):
    antall = tell_forekomster(elementer)
    beste = None beste_antall = 0 for e in antall:
        if antall[e] > beste_antall:
            beste = e beste_antall = antall[e]
    return beste

print(flest_av(["handletur", "lekselesing", "handletur", "besok", "handletur"]))
print(flest_av(["a", "b"]))
print(flest_av([]))

Utskrift:

handletur a
None

Slik ville sensor sett på det, steg for steg. Tellingen er halve oppgaven og
rettes for seg — en tell_forekomster som virker, gir uttelling selv om finn-beste skulle bli feil. Finn-beste-løkka gir uttelling for riktig struktur selv om du glemte å oppdatere beste_antall. Og tomsjekken løses her av startverdien None, uten en eneste ekstra if.

Andre linje viser uavgjort-regelen. Både a og b forekommer én gang, og
den strenge >-testen lar den første beholde plassen. Sier oppgaven ingenting om uavgjort, er begge svar forsvarlige — men skriv en kommentar om hvilken regel du valgte. Det er den ene legitime bruken av kommentarer i en besvarelse.

Og legg merke til at de to funksjonene ikke deler navn. Hadde den ytre også
hett tell_forekomster, ville koden blitt umulig å lese — og en metode som kaller noe med sitt eget navn, er en feilkilde du skal holde deg unna gjennom hele boka.

📝Oppgave 7
Eksamensnivå, sjanger D

En værstasjon har talt opp antall regnvær per måned i en ordbok fra månedsnavn til antall dager, for eksempel {"mai": 8, "juni": 14, "juli": 3}.

a) Skriv over_grensen(dager, grense) som returnerer en ny ordbok med
bare de parene der antallet er strengt større enn grensen.

b) Skriv travleste(dager) som returnerer månedsnavnet med flest regndager,
eller None hvis ordboka er tom.

c) Hva returnerer over_grensen(dager, 100), og er det et problem?

📝Oppgave 8
Eksamensnivå, sjanger I

En student skal telle hvor mange ganger hvert navn forekommer i en liste. Hun leverer:

def tell(navnene):
    antall = {}
    for navn in navnene:
        antall[navn] = antall[navn] + 1 return antall

a) Hva skjer når funksjonen kalles med ["ada", "bo", "ada"]?

b) Gi et konkret moteksempel og forklar i én setning hvorfor det går galt.

c) Rett feilen, og vis at den rettede funksjonen virker.

API- og konstruksjonsliste

Begrepsbank

Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.

Par

Én nøkkel med tilhørende verdi, altså én oppføring i ordboka. "glass": 7 er ett par.

len(ordbok) teller nøyaktig disse — ikke tegnene i nøklene og ikke summen av verdiene.

Når du sporer en ordbok gjennom en løkke, er det parene du skal skrive opp på nytt for hver runde. Det er den eneste måten å holde styr på hva som ble opprettet når.

Nøkler er unike

En ordbok kan ikke ha to like nøkler. Skriver du d["glass"] = 7 og deretter
d["glass"] = 9, står det 9 igjen — den gamle verdien er borte.

Det er nettopp derfor tilordning kan brukes både til å opprette og til å oppdatere: Python trenger ikke spørre deg hva du mente.

Verdiene har ingen slik regel. To ulike nøkler kan gjerne peke på samme tall, og i en telling gjør de det ofte.

Hva som kan være nøkkel

Tekster og tall er de to nøkkeltypene du trenger i dette faget, og de dekker alt du møter på eksamen: navn, koder, årstall, lengder.

En liste kan derimot ikke være nøkkel. Prøver du det, stopper programmet.
Grunnen er at nøkkelen må være en verdi som ikke kan endres etter at den er brukt — og en liste kan jo endres.

Verdien har ingen slik begrensning. Der kan du legge hva du vil, også lister og etter hvert dine egne objekter.

Innsettingsrekkefølge

Python husker rekkefølgen parene ble satt inn i, og både print(ordbok) og
for k in ordbok: følger den.

Det gjør sporingsoppgaver forutsigbare: den nøkkelen som ble opprettet først, kommer først. Merk at det er opprettelsen som teller — å overskrive en verdi flytter ikke nøkkelen bakerst.

Bygg likevel aldri en løsning på rekkefølgen. Ber oppgaven om «den første», er det som regel den første i inndataene, ikke i ordboka.

Teller-ordbok

En ordbok fra element til antall, bygget med teller-idiomet. Resultatet ser slik ut: {'glass': 3, 'kartong': 1}.

Kjenn den igjen på oppgaveteksten: «tell hvor mange ganger», «hvor mange av hver», «hvor mange ganger forekommer».

Den er halve svaret på ethvert «hvilken flest»-spørsmål. Andre halvdel er finn-beste-løkka, og de to skrives helst som hver sin funksjon — da rettes de hver for seg.

Grupperings-ordbok

En ordbok der hver verdi er en liste: {'kort': [12, 8], 'lang': [45]}.

Kjenn den igjen på oppgaveteksten: «samle», «gruppér etter», «hvilke hører til hver».

Forskjellen fra teller-ordboka er bare startverdien og oppdateringen — tom liste i stedet for 0, append i stedet for + 1. Skjelettet er ellers identisk, og det er verdt å se de to som ett mønster med to varianter.

Finn beste

Løkka som finner nøkkelen med størst verdi, med beste og beste_antall oppdatert sammen inne i if-en.

Den vanligste feilen er å oppdatere bare beste. Da sammenlignes alt mot startverdien 0 for alltid, og svaret blir den siste nøkkelen med positiv verdi.

Den nest vanligste er å returnere beste_antall når oppgaven ba om beste.
Les spørsmålet en siste gang før du skriver return: ba den om hvilken eller om hvor mange?

Ordbok mot liste

Valget avgjøres av ett spørsmål: slår du opp på plass eller på navn?

Er rekkefølgen selve dataene — køen, ruta, målingene time for time — er lista riktig. Skal du finne noe igjen på et navn, en kode eller et årstall, er ordboka riktig, og du slipper å lete.

En huskeregel: må du skrive en løkke bare for å finne det du skal bruke, har du sannsynligvis valgt feil struktur.

En-til-mange

Mønsteret der én nøkkel peker på flere ting: {nøkkel: [liste]}.

Det er formen grupperings-idiomet lager, og det er den samme formen den store klassemodelleringen bruker når ett objekt skal holde mange andre — et bibliotek med sine bøker, en klubb med sine medlemmer.

Kjenner du igjen strukturen her, kjenner du den igjen der. Forskjellen er bare at elementene i lista blir objekter i stedet for tall.

Å bygge en ny ordbok

Samme tre steg som for lister, med {} i stedet for []:

gamle = {"a": 3, "b": 9}
ny = {}
for k in gamle:
    if gamle[k] > 5:
        ny[k] = gamle[k]
print(ny)
print(gamle)

Utskrift:

{'b': 9}
{'a': 3, 'b': 9}

Merk at den opprinnelige ordboka er urørt — det er hele poenget med å bygge en ny. Å endre ordboka du fikk inn som parameter, er sjelden det oppgaven ber om, og det gir overraskelser hos den som kalte funksjonen.

Ordbok som parameter

En ordbok sendes inn i en funksjon som et hvilket som helst annet argument.

Inne i funksjonen er navnet et navn på den samme ordboka som utenfor, ikke en kopi. Leser du bare fra den, spiller det ingen rolle. Skriver du d[k] = v, endrer du kallerens ordbok også.

Den trygge stilen er den du har brukt her: les fra ordboka du fikk inn, bygg
en ny, og returner den nye.

`d[k] = d[k] + 1`

Linja som øker en teller. Den gjør tre ting i én: slår opp den gamle verdien, legger til én, og skriver resultatet tilbake på samme nøkkel.

Fordi høyresiden leses først, må nøkkelen finnes fra før. Det er hele grunnen til at if k not in d: d[k] = 0 står rett foran.

Kortformen d[k] += 1 betyr nøyaktig det samme, og begge er greie på eksamen.
Velg én av dem og hold deg til den gjennom hele besvarelsen.

Tom ordbok som svar

En funksjon som bygger en ordbok, returnerer {} når det ikke var noe å legge inn. Det er et riktig svar, ikke en feil.

Den som mottar svaret, må bare tåle det. En løkke over en tom ordbok kjører null runder og går fint. Et oppslag gjør det ikke.

Skal funksjonen din i stedet si «fant ingenting», er None det ærlige svaret — og det er nettopp derfor finn-beste starter med beste = None.

Å endre en ordbok mens du går gjennom den

Å endre verdiene i en løkke er trygt: for k in d: d[k] = d[k] * 2 virker som forventet.

Å legge til eller fjerne nøkler mens løkka går, er derimot ikke trygt.
Python kan stoppe med en feilmelding, fordi samlingen du løper gjennom, endrer størrelse under føttene på deg.

Regelen som holder: løp gjennom den ene samlingen, bygg den andre. Det er nøyaktig det både teller-idiomet og bygg-mønsteret gjør — de leser fra en liste og skriver til en ordbok.

Å telle uten ferdige hjelpemidler

Python har Counter, set(...), sum(...), max(..., key=...) og
d.get(k, 0), som gjør flere av oppgavene i dette kapitlet til én linje.

Boka bruker dem ikke. Fagets løsningsforslag skriver den eksplisitte løkka, fordi løkka er ferdigheten som måles — og på den store klassemodelleringen finnes det ingen ferdig funksjon å ty til.

Snarveiene er verdt å kjenne til, slik at du forstår kode andre har skrevet.
Men lær grunnformen først, og lever den på eksamen.

Repetisjon — de sju setningene som må sitte

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.

Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.