3.5 Ordbøker som oppslag, teller og gruppering
Ordboka som oppslagstabell, forekomstteller (d.get(k,0)+1) og grupperingsstruktur ({nøkkel: [elementer]}) — de tre mønstrene analyseoppgavene bruker.
Ordbøker er belagt i åtte av de ti eksamenssittingene i arkivet (2014–2019) — rundt 80 %. Det er den nest viktigste datastrukturen i faget, etter 2D-tabellen, og de to opptrer nesten alltid sammen: tabellen kommer inn fra fila, ordboka er det du gjør den om til når du skal telle eller gruppere.
Slik dukker kapitlet opp på settet:
- Sjanger G — analyse, altså «tell hvor mange av hver» eller «gruppér etter». Dette er tyngdepunktet i programmeringsoppgaven, 5–10 % per deloppgave. To mønstre går igjen år etter år: telleren og grupperingen, og begge står som egne kodemønstre lenger nede.
- Sjanger B — kodesporing, altså «hva skriver dette programmet ut?». En kort løkke som bygger opp en telleordbok, der du skal si hva ordboka inneholder til slutt, er en fast innpakning. Den gir 1–3 % per deloppgave.
Dette er det viktigste kapitlet i Del 3. Telleren d[k] = d.get(k, 0) + 1 er et mønster sensor forventer å se, og som du bør kunne skrive uten å tenke. Bruker du et kvarter ekstra på løkke 3, får du det igjen i hele Del 7.
Prioritet: dette må sitte (høyeste av bokas tre nivåer «må sitte», «må kunne», «bør kjenne til»). Regn med 50 minutter, delt i fire økter — de to siste er de tunge.
- kap. 3.1 — lister, append, in-testen og snarveiene max, sum og sorted. Ordboka bygger på dem, og grupperingen legger lister inn i ordboka.
- kap. 3.2 — 2D-tabeller, fordi ordboka som regel bygges fra en tabell som er lest inn fra fil.
- kap. 1.3 — løkker, og kap. 1.2 — betingelser.
Vil du se ordboka forklart i en annen innpakning først, dekker Ordbøker det samme stoffet i et parallelt emne.
To ord du trenger fra første setning: en nøkkel er det du slår opp på, og en verdi er det du får igjen. I en telefonliste er navnet nøkkelen og nummeret verdien.
Løkke 1 — ordboka som oppslagstabell (~12 min)
En fuglestasjon fører logg over hva som blir observert. Med lister må du lagre to ting i takt — artsnavnene i én liste og antallet i en annen — og for å finne ut hvor mange gråtroster som er sett, må du først lete opp hvor «gråtrost» står, og så hente det samme plassnummeret i den andre lista.
En ordbok fjerner hele letingen. Du slår opp direkte på artsnavnet og får antallet. Ordboka er altså en samling par: hver nøkkel peker på sin verdi.
Merk at «ordbok» er det norske ordet; i koden heter typen dict, som er kort for det engelske dictionary.
En ordbok er en samling par der hver nøkkel peker på én verdi. Du skriver den i krøllparenteser, med kolon mellom nøkkel og verdi og komma mellom parene: {'gråtrost': 12, 'linerle': 5}.
Den tomme ordboka er {} — ikke å forveksle med den tomme lista [] — og er ofte startpunktet: du oppretter den tom og fyller på underveis. En ordbok har ingen plassnumre; du kommer til verdiene bare gjennom nøklene.
Henter verdien som nøkkelen k peker på. antall['linerle'] gir 5 i eksempelet over.
Firkantparentesene ser ut som listeindeksering, men betyr noe helt annet: i ei liste er tallet inni en plass, i en ordbok er det inni en nøkkel. Finnes ikke nøkkelen, stopper programmet.
Samme skrivemåte gjør to jobber. Finnes ikke nøkkelen fra før, blir paret lagt inn som nytt; finnes den, blir den gamle verdien overskrevet.
Det er derfor antall['linerle'] = 6 virker uansett om linerla var registrert fra før. Og det er også derfor du aldri får IndexError når du legger noe inn i en ordbok: den vokser av seg selv, i motsetning til ei liste, der du må bruke append.
Gir True hvis k er en nøkkel i ordboka, ellers False. not in er negasjonen.
Merk at testen ser på nøklene, ikke på verdiene: i {'gråtrost': 12} er 'gråtrost' in d sann, mens 12 in d er usann. Vil du lete i verdiene, må du skrive 12 in d.values(). Testen er den vanlige måten å unngå at et oppslag krasjer.
Gir antall par i ordboka, som et heltall. len({'a': 1, 'b': 2}) er 2, ikke 4.
En tom ordbok har lengde 0. Fordi nøklene er unike, er len(d) samtidig svaret på «hvor mange forskjellige verdier har jeg sett?» — et spørsmål som dukker opp stadig vekk i analyseoppgaver.
Slår du opp en nøkkel som ikke finnes, stopper programmet med feilmeldingen KeyError: 'kjøttmeis'. Ordboka gjetter ikke, og den lager ingen tom plass for deg.
Dette er den vanligste kjørefeilen med ordbøker, og den har to motgifter: sjekke med in først, eller hente med .get, som gir en standardverdi i stedet for å stoppe. Merk at feilen bare gjelder lesing — d['kjøttmeis'] = 1 er alltid lov.
Fuglestasjonen har registrert 12 gråtroster, 5 linerler og 3 kjøttmeiser. Legg tallene i en ordbok, slå opp linerla, legg til 7 bokfinker, korriger gråtrosten til 14, og skriv ut hvor mange arter som er registrert.
Ordboka opprettes med de tre parene som allerede er kjent, og fylles på etterpå.
antall = {'gråtrost': 12, 'linerle': 5, 'kjøttmeis': 3}
print(antall['linerle'])
antall['bokfink'] = 7
antall['gråtrost'] = 14
print(antall)
print(len(antall))Utskrift:
5
{'gråtrost': 14, 'linerle': 5, 'kjøttmeis': 3, 'bokfink': 7}
4Legg merke til at de to tilordningene ser helt like ut, men gjør forskjellige ting: 'bokfink' fantes ikke og ble lagt inn som et nytt par, mens 'gråtrost' fantes og fikk verdien sin overskrevet. Det er derfor ordboka har fire par til slutt, ikke fem.
Legg også merke til hvordan Python skriver ut en ordbok: krøllparenteser, kolon mellom nøkkel og verdi, komma-mellomrom mellom parene, og tekstnøklene i fnutter. På en kodesporingsoppgave må du gjengi nøyaktig den formen.
(Innstegsoppgave, sjanger B — kodesporing, altså «hva skriver dette programmet ut?».) Skriv alle tre linjene nøyaktig, med krøllparenteser og fnutter der de hører hjemme.
priser = {'kaffe': 25, 'te': 20}
priser['kakao'] = 27
priser['te'] = 22
print(priser['kakao'])
print(len(priser))
print(priser)Henter verdien til nøkkelen k, men gir standardverdien i stedet for å stoppe hvis nøkkelen mangler. antall.get('ravn', 0) gir 0 når ravnen aldri er observert.
Tre presiseringer som avgjør kodesporingsoppgaver: metoden setter ikke nøkkelen inn i ordboka — den bare svarer, og ordboka er like stor etterpå. Finnes nøkkelen, blir standardverdien ignorert. Og dropper du standardverdien, altså d.get(k), får du None når nøkkelen mangler.
Fire feller går igjen:
- Å indeksere ordboka med et plassnummer. antall[0] betyr «verdien til nøkkelen 0», ikke «det første paret». Er det ingen nøkkel som er tallet 0, gir det KeyError.
- Å slå opp uten å sjekke. antall['ravn'] stopper programmet hvis ravnen mangler. Bruk if 'ravn' in antall: eller antall.get('ravn', 0).
- Å tro at .get legger nøkkelen inn. Den gjør ikke det. Skal nøkkelen faktisk inn, må du tilordne: antall['ravn'] = antall.get('ravn', 0) + 1.
- Å teste verdier med in. 12 in antall leter blant nøklene, ikke blant tallene. Verdisøk er 12 in antall.values().
Løkke 2 — å gå gjennom en ordbok (~10 min)
Så snart ordboka er bygget, skal den som regel skrives ut eller summeres. Da må du kunne gå gjennom den, og her har Python fire skrivemåter som gir hver sin ting: nøklene, verdiene, parene — eller nøklene i sortert rekkefølge.
Merk én ting med én gang: en ordbok går gjennom parene i den rekkefølgen de ble satt inn, ikke i alfabetisk rekkefølge. Det er en fast felle i kodesporing.
En for-løkke over en ordbok gir deg nøklene, én om gangen, i innsettingsrekkefølge. Verdien henter du inni løkka med d[k].
Det er lett å tro at løkka gir parene eller verdiene. Den gir nøklene, og det er nøyaktig det samme som for k in d.keys(). Vil du ha begge deler i løkkehodet, bruker du .items().
Gir en visning av alle nøklene, i innsettingsrekkefølge. Den kan brukes i en for-løkke og i en in-test, og gjøres om til ei ekte liste med list(d.keys()).
Utskrevet ser den slik ut: dict_keys(['gråtrost', 'linerle']) — altså ikke som ei vanlig liste. Skal utskriften bli pen, konverterer du med list(...) først.
Gir en visning av alle verdiene, i samme rekkefølge som nøklene. Verdiene er ikke unike — to arter kan godt ha samme antall.
Nytten er at snarveiene fra kap. 3.1 virker rett på den: sum(d.values()) gir totalen, og max(d.values()) gir den største verdien. Utskrevet ser den ut som dict_values([12, 5]).
Gir parene, ett om gangen, som tupler på formen (nøkkel, verdi). Det vanlige er å pakke dem ut direkte i løkkehodet: for art, tall in antall.items():.
Da har du begge deler tilgjengelig uten et ekstra oppslag inni løkka. Dette er den formen løsningsforslagene bruker når de skriver ut en hel ordbok. (Tupler er tema i kap. 3.6; her trenger du bare å vite at de kan pakkes ut i to variabler.)
sorted brukt på en ordbok gir ei liste med nøklene, sortert — alfabetisk for tekst, stigende for tall. Verdiene er ikke med.Dette er måten du får en rapport i fast rekkefølge: for k in sorted(d):. Uten den følger utskriften innsettingsrekkefølgen, som avhenger av hvilken rekkefølge fila kom i, og som sjelden er den du vil vise fram.
En ordbok kan ikke ha to like nøkler. Setter du inn et par med en nøkkel som finnes, erstattes den gamle verdien — det blir aldri to.
Konsekvensen er at en ordbok mister informasjon hvis du bruker den feil: skriver du d[art] = tall inne i en løkke over alle observasjoner, sitter du til slutt igjen bare med den siste verdien for hver art. Skal de legges sammen, må du telle (løkke 3); skal de samles, må du gruppere (løkke 4).
Nøkler må være uforanderlige verdier: tekst, heltall, desimaltall og tupler. Verdiene har ingen slik begrensning — de kan være tall, tekst, lister eller nye ordbøker.
I praksis er nøkkelen nesten alltid tekst (et navn, en kode, en dato) eller et heltall (et årstall, et nummer). Ei liste kan ikke være nøkkel, fordi den kan endres etterpå; trenger du en sammensatt nøkkel, bruker du et tuppel.
Med antall = {'gråtrost': 14, 'linerle': 5, 'kjøttmeis': 3, 'bokfink': 7}: skriv ut hvert par på en egen linje i alfabetisk rekkefølge, og deretter totalen og den største enkeltverdien.
Løkka går over sorted(antall), som gir nøklene sortert; verdien hentes med oppslag.
antall = {'gråtrost': 14, 'linerle': 5, 'kjøttmeis': 3, 'bokfink': 7}
for art in sorted(antall):
print(art.ljust(10) + str(antall[art]).rjust(4))
print('Totalt:', sum(antall.values()))
print('Flest av én art:', max(antall.values()))Utskrift:
bokfink 7
gråtrost 14
kjøttmeis 3
linerle 5
Totalt: 29
Flest av én art: 14Den samme utskriften uten sortering ville kommet i rekkefølgen gråtrost, linerle, kjøttmeis, bokfink — altså slik parene ble satt inn.
Vil du ha både nøkkel og verdi i løkkehodet, skriver du det slik i stedet:
antall = {'gråtrost': 14, 'linerle': 5, 'kjøttmeis': 3, 'bokfink': 7}
for art, tall in antall.items():
print(art, tall)Utskrift:
gråtrost 14
linerle 5
kjøttmeis 3
bokfink 7Denne varianten følger innsettingsrekkefølgen. Begge former gir full pott — velg .items() når du trenger begge deler og ikke bryr deg om rekkefølgen, og sorted(d) når rapporten skal komme i fast orden.
Hva skriver programmet ut? Skriv alle linjene nøyaktig.
lager = {'skrue': 40, 'mutter': 15, 'spiker': 40}
for vare in lager:
if lager[vare] > 20:
print(vare, lager[vare])
print(len(lager))
print(sum(lager.values()))
print('mutter' in lager, 15 in lager)Løkke 3 — telleren (~14 min)
— naturlig pausepunkt —
Nå kommer kapitlets viktigste mønster. Fuglestasjonen leverer ikke et ferdig antall; den leverer en rå observasjonsliste der hver observasjon er ett artsnavn, og samme art står der like mange ganger som den er sett. Oppgaven er å gjøre den lista om til en ordbok fra art til antall.
Det høres lite ut, men det er nøyaktig denne øvelsen som kommer igjen som «tell hvor mange stemmer hver kandidat fikk», «tell hvor mange ganger hver terningverdi kom» og «tell hvor mange lån hver låntaker har». Domenet roterer, øvelsen er den samme.
Mønsteret, i tre linjer:
1. Opprett en tom ordbok: d = {}.
2. Gå gjennom lista: for k in liste:.
3. Legg én til det som allerede står der: d[k] = d.get(k, 0) + 1.
Hele poenget med .get(k, 0) er å slippe å behandle første forekomst som et spesialtilfelle: har vi ikke sett k før, later .get som om tallet var 0, og resultatet blir 1.
Den eksplisitte varianten skriver spesialtilfellet ut. Blokken under er et fragment — de fire linjene som står inne i løkka, ikke et program som kan kjøres alene:
if k in d:
d[k] = d[k] + 1
else:
d[k] = 1De to gjør nøyaktig det samme, og begge gir full pott. Ettlinjeversjonen er den løsningsforslagene bruker, og den du bør kunne skrive uten å tenke.
Skal du telle noe annet enn 1 om gangen — for eksempel legge sammen beløp per kunde — bytter du bare ut ettallet: d[k] = d.get(k, 0) + belop.
Begge legger én til den verdien som allerede står på nøkkelen k, og += er bare en kortform for den første.
Fella er at de forutsetter at nøkkelen finnes. Er k ny, stopper begge med KeyError, fordi Python må lese den gamle verdien før den kan legge til. Det er nettopp derfor tellermønsteret bruker .get(k, 0): det gjør første forekomst ufarlig.
Dagens observasjoner ligger som ei liste der hvert element er ett artsnavn: ['linerle', 'gråtrost', 'linerle', 'bokfink', 'gråtrost', 'linerle']. Lag en ordbok fra art til antall — først med .get-mønsteret, så med den eksplisitte varianten.
Først mønsteret slik det står i kodemønsteret over:
def tell(liste):
d = {}
for k in liste:
d[k] = d.get(k, 0) + 1
return d
obs = ['linerle', 'gråtrost', 'linerle', 'bokfink', 'gråtrost', 'linerle']
print(tell(obs))Utskrift:
{'linerle': 3, 'gråtrost': 2, 'bokfink': 1}Og her er den samme funksjonen med spesialtilfellet skrevet ut:
def tell(liste):
d = {}
for k in liste:
if k in d:
d[k] = d[k] + 1
else:
d[k] = 1
return d
obs = ['linerle', 'gråtrost', 'linerle', 'bokfink', 'gråtrost', 'linerle']
print(tell(obs))Utskrift:
{'linerle': 3, 'gråtrost': 2, 'bokfink': 1}Identisk resultat, og begge gir full pott. Den korte er raskere å skrive under tidspress og er formen løsningsforslagene bruker; den lange er lettere å forklare høyt.
Legg merke til rekkefølgen i utskriften: linerla kommer først fordi den ble observert først, ikke fordi den er flest eller først i alfabetet.
Hva skriver programmet ut? Skriv begge linjene nøyaktig, med krøllparenteser og fnutter.
bokstaver = ['R', 'G', 'R', 'B', 'G', 'R']
d = {}
for b in bokstaver:
d[b] = d.get(b, 0) + 1
print(d)
print(d.get('S', 0), len(d))En sykkelutleie logger hver utleie som én linje i lista utleier, der hvert element er navnet på stasjonen sykkelen ble hentet fra.
a) Skriv tell_per_stasjon(utleier) som returnerer en ordbok fra stasjonsnavn til antall utleier.
b) Skriv stasjoner_over(d, grense) som returnerer ei sortert liste med de stasjonsnavnene som har flere utleier enn grense.
c) Hva returnerer tell_per_stasjon([]), og hvorfor er det riktig svar?
Test med utleier = ['Torget', 'Havna', 'Torget', 'Stadion', 'Havna', 'Torget'] og grensen 1.
Snarveiene fra kap. 3.1 virker rett på verdiene i en ordbok. sum(d.values()) gir totalen, og max(d.values()) gir den største enkeltverdien.
Det de ikke gir, er hvilken nøkkel som har den største verdien — max på verdiene har mistet nøkkelen underveis. Skal du ha navnet, må du enten gå gjennom ordboka med en finn-beste-løkke, eller først finne toppverdien og så lete opp nøkkelen som har den. Vær også oppmerksom på at max stopper med ValueError på en tom ordbok.
Med telleordboka {'linerle': 3, 'gråtrost': 2, 'bokfink': 1}: finn den største verdien, og finn hvilken art som har den. Vis både snarveien og løkkevarianten, og få løkka til å tåle at ordboka er tom.
Den største verdien alene er en snarvei:
antall = {'linerle': 3, 'gråtrost': 2, 'bokfink': 1}
print(max(antall.values()))Utskrift:
3Skal du også ha navnet, går du gjennom ordboka og husker den beste underveis:
def flest(antall):
beste = None
for art in antall:
if beste is None or antall[art] > antall[beste]:
beste = art
return beste
antall = {'linerle': 3, 'gråtrost': 2, 'bokfink': 1}
print(flest(antall))
print(flest({}))Utskrift:
linerle
NoneBegge gir full pott — men de svarer på hvert sitt spørsmål. Snarveien gir tallet, løkka gir navnet.
Legg merke til beste = None og testen beste is None or .... Den gjør to ting på én gang: den slipper å gjette en startverdi som er lav nok, og den gjør at funksjonen returnerer None i stedet for å krasje når ordboka er tom. Det er et fullscore-krav i de store programmeringsoppgavene, og mønsteret går igjen i hele Del 7.
Løkke 4 — gruppering (~14 min)
— naturlig pausepunkt —
Telleren svarer på «hvor mange». Grupperingen svarer på «hvilke». Et kurssenter har ei liste over påmeldinger, der hver påmelding er et par av kursnavn og deltakernavn, og spørsmålet er hvem som går på hvert kurs.
Svaret er en ordbok der verdien selv er ei liste: {'Førstehjelp': ['Aas', 'Berg'], 'Klatring': ['Dahl']}. Mønsteret er nesten som telleren, med én ekstra ting å passe på: lista må finnes før du kan legge noe i den.
Mønsteret, i fire linjer:
1. Opprett en tom ordbok: d = {}.
2. Gå gjennom dataene og finn nøkkelen k og elementet v.
3. Opprett den tomme lista hvis nøkkelen er ny: if k not in d: d[k] = [].
4. Legg elementet i lista: d[k].append(v).
Steg 3 er det som skiller en riktig gruppering fra en som krasjer. Hopper du over det, prøver d[k].append(v) å lese en nøkkel som ikke finnes, og programmet stopper med KeyError.
Merk hvorfor du ikke kan bruke .get her på samme måte som i telleren: d.get(k, []).append(v) ser riktig ut, men når nøkkelen mangler, legger den elementet i ei helt fersk liste som aldri havner inn i ordboka — og elementet er borte. Verdien må inn i ordboka først.
Gjør steg 3 og oppslaget i ett: setter d[k] til den tomme lista hvis nøkkelen mangler, og returnerer uansett den lista som ligger på nøkkelen.
Hele grupperingen blir da én linje: d.setdefault(k, []).append(v). Forskjellen fra .get er avgjørende: setdefault legger nøkkelen inn i ordboka, mens .get bare svarer. Begge skrivemåtene av grupperingen er godkjent; velg den du husker riktig.
Påmeldingene ligger som to lister i takt: kurs med kursnavnet og deltaker med navnet på den påmeldte. Lag en ordbok fra kursnavn til liste over deltakere, med begge skrivemåtene av grupperingsmønsteret.
Først den eksplisitte formen, der den tomme lista opprettes i et eget steg:
def grupper(kurs, deltaker):
d = {}
for i in range(len(kurs)):
k = kurs[i]
if k not in d:
d[k] = []
d[k].append(deltaker[i])
return d
kurs = ['Førstehjelp', 'Klatring', 'Førstehjelp', 'Padling', 'Klatring']
deltaker = ['Aas', 'Berg', 'Dahl', 'Eng', 'Five']
print(grupper(kurs, deltaker))Utskrift:
{'Førstehjelp': ['Aas', 'Dahl'], 'Klatring': ['Berg', 'Five'], 'Padling': ['Eng']}Og her er den samme funksjonen med setdefault, som slår de to stegene sammen:
def grupper(kurs, deltaker):
d = {}
for i in range(len(kurs)):
d.setdefault(kurs[i], []).append(deltaker[i])
return d
kurs = ['Førstehjelp', 'Klatring', 'Førstehjelp', 'Padling', 'Klatring']
deltaker = ['Aas', 'Berg', 'Dahl', 'Eng', 'Five']
print(grupper(kurs, deltaker))Utskrift:
{'Førstehjelp': ['Aas', 'Dahl'], 'Klatring': ['Berg', 'Five'], 'Padling': ['Eng']}Identisk resultat, og begge gir full pott.
Legg merke til at hver verdi er ei ekte liste, også når det bare er én deltaker: 'Padling': ['Eng'], ikke 'Padling': 'Eng'. Det er ikke pedanteri — koden som senere skal telle deltakere per kurs med len(d[k]), forutsetter at verdien alltid er ei liste.
Med grupperingen på plass er oppfølgingsspørsmålene korte:
d = {'Førstehjelp': ['Aas', 'Dahl'], 'Klatring': ['Berg', 'Five'], 'Padling': ['Eng']}
for k in sorted(d):
print(k.ljust(14) + str(len(d[k])).rjust(2) + ' ' + ', '.join(d[k]))Utskrift:
Førstehjelp 2 Aas, Dahl
Klatring 2 Berg, Five
Padling 1 EngTre feil koster poeng i akkurat dette mønsteret:
- Glemt å opprette den tomme lista. d[k].append(v) på en ny nøkkel gir KeyError. Enten if k not in d: d[k] = [] foran, eller setdefault.
- Tilordning i stedet for append. d[k] = v overskriver, så du sitter igjen med bare det siste elementet i hver gruppe — informasjonen er borte, og det ser ut som koden virker.
- d.get(k, []).append(v). Ser ut som telleren, men elementet havner i ei liste som aldri kommer inn i ordboka. Gruppen blir stående tom.
Kontrollen er enkel: kjør mønsteret i hodet på to elementer med samme nøkkel. Ender du med begge i lista, er koden riktig.
Et gjenbruksverksted registrerer hver reparasjon som to lister i takt: type med hva slags gjenstand det var, og dager med hvor mange dager reparasjonen tok.
a) Skriv grupper_dager(type, dager) som returnerer en ordbok fra gjenstandstype til lista over reparasjonstider for den typen.
b) Skriv snitt_per_type(gruppert) som tar ordboka fra a) og returnerer en ny ordbok fra type til gjennomsnittlig antall dager.
Test med type = ['sykkel', 'lampe', 'sykkel', 'stol', 'lampe', 'sykkel'] og dager = [3, 1, 5, 2, 4, 4].
Mønsteret, i fire steg:
1. Finn kolonnene dynamisk, aldri med hardkodede tall: kn = tabell[0].index('navn') og kv = tabell[0].index('verdi').
2. Opprett en tom ordbok.
3. Gå gjennom radene fra og med 1 — for rad in tabell[1:]: — slik at overskriftsraden ikke havner i ordboka.
4. Sett inn paret: d[rad[kn]] = rad[kv], eventuelt med int() rundt verdien.
Steg 1 er fullscore-kravet fra kap. 3.2: du skal ikke anta at kolonnen står på en bestemt plass (feilkode #4). Steg 3 er den vanligste utelatelsen — glemmer du [1:], får du et par som heter 'navn': 'verdi' midt i ordboka.
Skal flere rader kunne ha samme nøkkel, er dette gruppering og ikke oppslag: da bruker du mønsteret over i stedet for tilordning.
En innlest tabell over pantesatser har overskriftsraden ['emballasje', 'sats', 'materiale']. Lag en ordbok fra emballasjetype til sats i øre, uten å anta hvilken plass kolonnene har.
Kolonnene finnes med .index på overskriftsraden, og radene gås gjennom fra og med den andre.
def lag_satsordbok(tabell):
ke = tabell[0].index('emballasje')
ks = tabell[0].index('sats')
d = {}
for rad in tabell[1:]:
d[rad[ke]] = int(rad[ks])
return d
tabell = [['emballasje', 'sats', 'materiale'],
['liten flaske', '200', 'plast'],
['stor flaske', '300', 'plast'],
['boks', '200', 'metall']]
satser = lag_satsordbok(tabell)
print(satser)
print(satser['boks'])Utskrift:
{'liten flaske': 200, 'stor flaske': 300, 'boks': 200}
200Tre ting er verdt å merke seg. tabell[0].index('sats') gir 1 her, men koden virker like godt om kolonnene kommer i en annen rekkefølge neste gang — det er hele poenget med å slå opp dynamisk. tabell[1:] hopper over overskriftsraden. Og int(rad[ks]) er nødvendig fordi verdier lest fra fil alltid er tekst; uten den ville satser['boks'] + 100 gitt en feilmelding i stedet for 300.
Legg til slutt merke til at to emballasjetyper har samme sats. Det er helt greit — det er bare nøklene som må være unike.
Velg ordbok når du skal slå opp på et navn, telle forekomster eller gruppere. Nøkkelen er da det du kjenner, og verdien det du vil finne.
Velg liste når rekkefølgen er en del av dataene, når du skal sortere eller når du bare skal gå gjennom alt. En 2D-tabell fra fil er alltid ei liste av lister først; ordboka lager du av den når analysen krever oppslag.
En tommelfingerregel som holder på eksamen: sier oppgaven «per», «for hver» eller «hvor mange av hver», skal svaret nesten alltid være en ordbok.
En kommune har lest inn en tabell over avfallsinnleveringer. Overskriftsraden er ['sone', 'fraksjon', 'kilo'], og hver av de øvrige radene er én innlevering. Kolonnerekkefølgen kan være en annen neste gang fila kommer.
a) Skriv kilo_per_fraksjon(tabell) som returnerer en ordbok fra fraksjon til totalt antall kilo.
b) Skriv soner_per_fraksjon(tabell) som returnerer en ordbok fra fraksjon til ei liste over sonene som har levert den — uten duplikater.
c) Skriv storste_fraksjon(d) som tar ordboka fra a) og returnerer navnet på fraksjonen med flest kilo, eller None hvis ordboka er tom.
Test med tabellen under.
tabell = [['sone', 'fraksjon', 'kilo'],
['nord', 'papir', '120'],
['sør', 'glass', '45'],
['nord', 'papir', '80'],
['øst', 'glass', '60'],
['nord', 'glass', '25']]
print(len(tabell))Hva skriver programmet ut? Skriv begge linjene nøyaktig.
par = [('mat', 'ris'), ('kles', 'sokk'), ('mat', 'ost'), ('mat', 'ris')]
d = {}
for k, v in par:
if k not in d:
d[k] = []
d[k].append(v)
print(d)
print(len(d), len(d['mat']))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 Norges teknisk-naturvitenskapelige universitet. Dette er ikke offisielt studiemateriell. Les mer.