3.2 2D-lister (tabeller)
Liste av lister som en tabell med rader og kolonner — den sentrale datastrukturen i programmeringsoppgaven, inkludert dynamisk kolonneoppslag.
To sjangre møter deg her:
- Sjanger B — kodesporing, altså «hva skriver dette programmet ut?». Matrisesporing er en fast innpakning: transponér tabellen, legg en ramme av nuller rundt, eller snu alle 0-er til 1-ere. 1–3 % per deloppgave.
- Sjanger G — analyse, altså «finn den beste raden / hent ut en kolonne / tell noe per rad». Dette er tyngdepunktet: 5–10 % per deloppgave, og programmeringsoppgaven som helhet er 50–60 % av settet.
Ett grep gir uttelling hvert eneste år: å finne kolonnen ved NAVN i overskriftsraden, tabell[0].index('pris'), i stedet for å skrive et fast kolonnenummer. Løsningsforslagene nevner det eksplisitt som et krav for full score. Lær det her, og du har det med deg gjennom hele Del 7.
Prioritet: dette må sitte (høyeste av bokas tre nivåer «må sitte», «må kunne», «bør kjenne til»). Kapitlet tar ca. 50 minutter og har fire løkker med tidsanslag — det tåler godt å deles på to økter, med et naturlig skille etter kolonnedelen.
- kap. 3.1 — lister, indeksering, utsnitt, append og .index. Alt i dette kapitlet er de samme operasjonene, brukt to ganger.
- kap. 1.3 — løkker og range, særlig nøstede løkker.
- kap. 1.1 — konvertering med int() og float(). Tabeller lest fra fil inneholder tekst, også der det ser ut som tall.
Vil du se nøstede strukturer i en annen innpakning, dekker Dictionaries og nøstede dictionaries beslektet stoff i et parallelt emne.
Løkke 1 — tabellen er ei liste med lister inni (~10 min)
Værstasjonene rapporterer tre ting hver: navn, døgntemperatur og nedbør. Én stasjon er ei liste med tre verdier. Fire stasjoner blir da ei liste med fire slike lister — og det er hele ideen.
En 2D-liste er en liste der hvert element selv er ei liste. Den ytre lista er radene; den indre lista er cellene i én rad. Ordet «2D» betyr bare at du trenger to tall for å peke på én verdi: hvilken rad, og hvilken kolonne.
Nesten alle tabeller i dette emnet har en overskriftsrad øverst — rad 0 — med kolonnenavnene. Den er ikke data, men den er nøkkelen til robust kode, som du ser om litt.
Ei liste der hvert element selv er ei liste. Den ytre lista er radene, den indre er cellene i raden. På norsk sier vi 2D-liste eller tabell, ikke «matrise» — det ordet reserveres for de tilfellene der oppgaven faktisk handler om matematiske matriser.
Radene trenger ikke være like lange, men i praksis er de det, fordi tabellen kommer fra en fil med samme antall felt på hver linje.
Verdien i rad i, kolonne j. Rekkefølgen er alltid rad først, kolonne etterpå — tabell[i] gir hele raden som ei liste, og [j] plukker deretter ut én celle av den raden.
Du kan også tilordne: tabell[2][1] = '5' endrer én celle. Bytter du om på de to tallene, får du enten feil verdi eller IndexError, og det er den vanligste feilen i kapitlet.
Legger ei hel liste inn som en ny rad bakerst i tabellen. Dette er måten tabeller bygges på: tabell = [] først, så én append per linje du leser.
Argumentet må være ei liste. tabell.append('Voll') legger inn en tekststreng som rad, og da vil rad[1] gi bokstaven o i stedet for en kolonneverdi.
Gir antall RADER, fordi den ytre lista har én plass per rad. Cellene telles ikke.
Har tabellen en overskriftsrad, er antall DATArader len(tabell) - 1. Det er verdt å skrive ned, for det er en av de faste av-med-én-fellene.
Gir hvor mange verdier den FØRSTE raden inneholder, altså antall kolonner når alle radene er like lange.
Er radene ulikt lange, sier dette tallet bare noe om rad 0. Da bruker du len(rad) eller len(tabell[i]) inne i løkka i stedet.
Legg målingene fra Blindern (4 grader, 12 mm), Voll (2 grader, 31 mm) og Tromsø (−3 grader, 7 mm) i en tabell med overskriftsrad. Skriv ut hele tabellen, én enkelt celle, hele rad 2, og størrelsen på tabellen.
Verdiene skrives som tekst her, akkurat slik de ville kommet fra en fil. Det er med vilje: da må du huske konverteringen når du skal regne.
vaer = [['stasjon', 'temp', 'nedbor'],
['Blindern', '4', '12'],
['Voll', '2', '31'],
['Tromso', '-3', '7']]
print(vaer)
print(vaer[1][0])
print(vaer[2])
print(len(vaer), len(vaer[0]))Utskrift:
[['stasjon', 'temp', 'nedbor'], ['Blindern', '4', '12'], ['Voll', '2', '31'], ['Tromso', '-3', '7']]
Blindern
['Voll', '2', '31']
4 3Legg merke til hvordan hele tabellen skrives ut: som ei liste med lister inni, hakeparentes i hakeparentes. vaer[1][0] er 'Blindern' — rad 1 er første datarad, fordi rad 0 er overskriftene. Og vaer[2] gir HELE raden som liste, ikke én verdi.
Tabellen har fire rader og tre kolonner, men bare TRE datarader: len(vaer) - 1.
Fra nå av bruker boka noen korte navn i to betydninger. Blir de blandet, blir koden feil på en måte som er vanskelig å se.
- i er radindeksen i dette kapitlet. I kap. 1.3 var den bare «løkkevariabelen». Når begge finnes i samme kode, skriver boka i for rad og j for kolonne.
- rad er HELE lista for én rad — ikke én verdi i raden. Skriver du for rad in tabell: og deretter print(rad), får du en liste med hakeparenteser.
- k brukes her for en kolonneindeks. I kap. 3.5 betyr k en nøkkel i en ordbok. Boka sier alltid hvilken betydning som gjelder.
- «tabell» er 2D-lista i koden. Senere, i utskriftskapitlene, betyr «tabell» også den pene kolonneutskriften på skjermen. De to henger sammen, men er ikke det samme.
(Innstegsoppgave, sjanger B — kodesporing.) Hva skriver programmet ut? Skriv alle tre linjene nøyaktig.
t = [[1, 2, 3], [4, 5, 6]]
print(t[0][2])
print(t[1])
print(len(t))Løkke 2 — å gå gjennom tabellen (~12 min)
Det finnes to måter å løpe gjennom en tabell på, og de brukes til hver sitt.
Den første er for rad in tabell:. Den gir deg én hel rad om gangen og er den du velger når du bare skal lese. Den andre er for i in range(len(tabell)):, som gir deg radNUMMERET. Den trenger du når du skal hoppe over overskriftsraden, sammenligne naborader, eller endre celler.
Går gjennom tabellen rad for rad. Inne i løkka er rad ei liste med verdiene i den ene raden, og du henter kolonneverdier med rad[j].
Denne formen gir deg ikke radnummeret. Trenger du det — for eksempel for å hoppe over overskriftsraden — bruker du indeksformen i stedet, eller du starter på tabell[1:].
Går gjennom radNUMRENE 0, 1, … opp til siste rad. Inne i løkka henter du raden med tabell[i] og en celle med tabell[i][j].
Vil du hoppe over overskriftsraden, starter du på 1: for i in range(1, len(tabell)):. Vil du sammenligne hver rad med den neste, stopper du én tidligere: range(len(tabell) - 1).
To løkker inni hverandre: den ytre går over radene, den indre over kolonnene i den raden. Kroppen kjøres én gang per celle, altså rader ganger kolonner ganger.
Standardformen er for i in range(len(tabell)): og inni den for j in range(len(tabell[i])):. Å bruke len(tabell[i]) og ikke len(tabell[0]) gjør koden riktig også når radene er ulikt lange.
Den første raden i tabellen, tabell[0], når den inneholder kolonnenavn i stedet for data. Dataene starter da på rad 1.
Overskriftsraden er ikke i veien — den er verktøyet. Den lar deg slå opp hvilken kolonne et navn har, og dermed skrive kode som virker selv om kolonnene kommer i en annen rekkefølge neste gang.
Med værtabellen fra eksempel 1: skriv ut én linje per stasjon på formen «Blindern: 12 mm», uten å ta med overskriftsraden. Regn deretter ut samlet nedbør.
Utskriften trenger ikke radnummeret, så for rad in vaer[1:]: er nok — utsnittet hopper over rad 0. Summen krever konvertering, fordi tallene ligger som tekst.
vaer = [['stasjon', 'temp', 'nedbor'],
['Blindern', '4', '12'],
['Voll', '2', '31'],
['Tromso', '-3', '7']]
for rad in vaer[1:]:
print(rad[0] + ': ' + rad[2] + ' mm')
total = 0
for rad in vaer[1:]:
total = total + int(rad[2])
print('Samlet nedbør:', total, 'mm')Utskrift:
Blindern: 12 mm
Voll: 31 mm
Tromso: 7 mm
Samlet nedbør: 50 mmTo detaljer avgjør poeng her. For det første vaer[1:] — uten utsnittet ville overskriftsraden blitt skrevet ut som om den var en stasjon, og int('nedbor') ville stoppet programmet. For det andre int(rad[2]): verdiene er tekst, og '12' + '31' ville gitt '1231' i stedet for 43 (feilkode #1 — glemt konvertering).
Du kunne like gjerne skrevet for i in range(1, len(vaer)): og brukt vaer[i]. Begge gir full uttelling.
rad[1] fordi temperaturen «pleier» å stå der, virker koden på akkurat den fila, og bare den. Løsningsforslagene krever eksplisitt at kolonnen finnes ved navn. Det er neste løkke i dette kapitlet.Ombyttede indekser. tabell[j][i] i stedet for tabell[i][j] gir enten feil celle eller IndexError — og i en kvadratisk tabell får du ingen feilmelding i det hele tatt, bare gale tall. Si «rad, kolonne» høyt hver gang du skriver to hakeparenteser.
Overskriftsraden som datarad. for rad in tabell: tar med rad 0. Konverteringen int(rad[1]) stopper da programmet med ValueError: invalid literal for int(). Bruk tabell[1:] eller range(1, len(tabell)).
Av-med-én ved naborader (feilkode #5 — bomme med én på en løkkegrense): skal du sammenligne rad i med rad i + 1, må løkka stoppe på range(len(tabell) - 1), ellers peker den siste sammenligningen utenfor tabellen.
Hva skriver programmet ut? Husk at end=' ' bytter ut linjeskiftet med et mellomrom, og at print() uten argumenter skriver et linjeskift.
t = [[1, 2], [3, 4], [5, 6]]
sum_diag = 0
for i in range(len(t)):
for j in range(len(t[i])):
if i == j:
sum_diag = sum_diag + t[i][j]
print(t[i][j], end=' ')
print()
print(sum_diag)Løkke 3 — kolonner, og kolonnen du finner ved navn (~12 min)
— naturlig pausepunkt —
En 2D-liste er lagret radvis. Vil du ha en hel kolonne, må du plukke ut samme plass fra hver rad. Det er tre linjer med løkke, eller én med den korte formen Python kaller en listekomprehensjon.
Og så kommer grepet som gir uttelling hvert år: i stedet for å skrive kolonnenummeret rett inn, slår du det opp i overskriftsraden.
Å hente ut alle verdiene i én kolonne: opprett en tom liste, gå gjennom radene, og legg til rad[k] for hver rad.
Formen er kolonne = [] og deretter for rad in tabell[1:]: kolonne.append(rad[k]). Utsnittet [1:] er der fordi overskriftsraden ikke er data.
En kortform for «bygg ei liste ved å gjøre det samme med hvert element». Den leses baklengs: ta for rad in tabell, og samle opp rad[k] for hver av dem.
Den gjør nøyaktig det samme som løkka med append, på én linje, og brukes fritt i løsningsforslagene. Begge former gir full pott — velg den du selv leser lettest under tidspress.
Slår opp hvilken plass kolonnenavnet navn har i overskriftsraden, og gir indeksen tilbake. Deretter bruker du den indeksen mot alle dataradene.
Dette er selve robusthetsgrepet: koden virker uansett hvilken rekkefølge kolonnene står i, og den slutter å virke med en tydelig ValueError hvis kolonnen mangler — i stedet for å regne videre på feil tall. Finnes ikke navnet, sjekker du med if navn in tabell[0]: først.
Hent ut alle temperaturene fra værtabellen. Gjør det først med en løkke, så med kortformen, og så robust — slik at koden virker selv om kolonnene skulle bytte plass.
Først den eksplisitte løkka, med kolonnenummeret skrevet rett inn:
vaer = [['stasjon', 'temp', 'nedbor'],
['Blindern', '4', '12'],
['Voll', '2', '31'],
['Tromso', '-3', '7']]
temp = []
for rad in vaer[1:]:
temp.append(rad[1])
print(temp)Utskrift:
['4', '2', '-3']Samme sak på én linje:
vaer = [['stasjon', 'temp', 'nedbor'],
['Blindern', '4', '12'],
['Voll', '2', '31'],
['Tromso', '-3', '7']]
temp = [rad[1] for rad in vaer[1:]]
print(temp)Utskrift:
['4', '2', '-3']Begge har den samme svakheten: tallet 1. Kommer fila neste gang med nedbør før temperatur, gir koden feil svar uten å si fra. Den robuste varianten spør overskriftsraden:
vaer = [['stasjon', 'temp', 'nedbor'],
['Blindern', '4', '12'],
['Voll', '2', '31'],
['Tromso', '-3', '7']]
k = vaer[0].index('temp')
temp = [int(rad[k]) for rad in vaer[1:]]
print(k)
print(temp)Utskrift:
1
[4, 2, -3]Her er k regnet ut, ikke gjettet — og verdiene er konvertert til tall med det samme, så de kan brukes i regning. Alle tre løsningene gir poeng, men bare den siste gir full score på robusthetsdeloppgavene. Vane deg til den nå; hele Del 7 bygger på den.
En strømprisoversikt ligger som en tabell med overskriftsrad, for eksempel [['sone', 'pris', 'forbruk'], ['NO1', '78', '410'], ['NO3', '31', '520']].
a) Skriv hent_kolonne(tabell, navn) som finner kolonneindeksen ved hjelp av overskriftsraden og returnerer ei liste med verdiene i den kolonnen — uten overskriften, og uten å anta hvilken plass kolonnen har.
b) Gjør funksjonen robust mot at kolonnen ikke finnes: returnér ei tom liste i stedet for å stoppe programmet.
Løkke 4 — transponering og cellevis endring (~10 min)
Å transponere en tabell er å bytte om rader og kolonner: det som sto i rad i, kolonne j, står etterpå i rad j, kolonne i. En tabell med 3 rader og 2 kolonner blir til en med 2 rader og 3 kolonner.
Dette er en yndet sporingsoppgave, fordi den tvinger deg til å holde styr på begge indekser samtidig. Den er også nyttig i praksis: etter en transponering er hver kolonne blitt en rad, og da kan du bruke max og sum rett på den.
Å bygge en ny tabell der ny[j][i] er den gamle gammel[i][j]. Antall rader og kolonner bytter plass.
Standardoppskriften er å løkke over kolonneindeksene ytterst og radindeksene innerst, og bygge én ny rad per gammel kolonne. Den nye tabellen er uavhengig av den gamle.
Skriver verdien v inn i rad i, kolonne j. Tabellen endres på plass, og ingen ny tabell lages.
Merk at tabell[i] = ny_rad bytter ut HELE raden. Det er en annen operasjon, og en vanlig kilde til forvirring når begge står i samme løkke.
En tabell der radene har ulikt antall kolonner. Den er fullt lovlig i Python, men gjør len(tabell[0]) upålitelig som kolonnetall.
Trygg kode bruker len(rad) eller len(tabell[i]) inne i løkka i stedet. Fildata er som regel jevne — men en tom siste linje i fila gir en tom rad, og da stopper rad[1] programmet.
ny = tabell[:] gir en ny ytre liste, men radene inni er fortsatt de SAMME listene. Endrer du en celle i ny, endres den i tabell også.En ekte, uavhengig kopi bygges rad for rad: opprett ny = [] og legg til rad[:] for hver rad. Dette er feilkode #15, aliasing, i den formen som er vanskeligst å oppdage.
Gjør tabellen [[1, 2], [3, 4], [5, 6]] — tre rader, to kolonner — om til en tabell med to rader og tre kolonner.
Den nye tabellen får én rad per gammel KOLONNE. Derfor går den ytre løkka over kolonneindeksene.
t = [[1, 2], [3, 4], [5, 6]]
ny = []
for j in range(len(t[0])):
rad = []
for i in range(len(t)):
rad.append(t[i][j])
ny.append(rad)
print(ny)
print(len(t), len(t[0]))
print(len(ny), len(ny[0]))Utskrift:
[[1, 3, 5], [2, 4, 6]]
3 2
2 3Tre ting er verdt å merke seg. rad = [] står INNE i den ytre løkka — flyttes den ut, samles alt i én lang rad. Indeksene i t[i][j] står i vanlig rekkefølge selv om løkkene er byttet om; det er nettopp løkkenes rekkefølge som gjør transponeringen. Og størrelsene bytter plass: 3 ganger 2 blir 2 ganger 3.
Skal du lage en tom tabell fylt med nuller, er det fristende å skrive [[0] * 3] * 2. Den ytre gjentakelsen kopierer ikke raden — den lager to PEKERE til den samme raden, og en endring i den ene synes i den andre. Det er feilkode #15, aliasing, i 2D-form.
t = [[0] * 3] * 2
t[0][0] = 9
print(t)Utskrift:
[[9, 0, 0], [9, 0, 0]]Bygg tabellen med en løkke i stedet, så hver rad er sin egen liste:
t = []
for i in range(2):
t.append([0] * 3)
t[0][0] = 9
print(t)Utskrift:
[[9, 0, 0], [0, 0, 0]]Den indre [0] * 3 er trygg, fordi tallene ikke kan endres innenfra. Det er bare gjentakelse av LISTER som gir aliaser.
Hva skriver programmet ut?
t = [[0, 1], [1, 0], [1, 1]]
for i in range(len(t)):
for j in range(len(t[i])):
t[i][j] = 1 - t[i][j]
print(t)
print(t[2])En resirkuleringsstasjon fører statistikk per måned i en tabell med overskriftsrad, for eksempel [['maned', 'glass', 'metall'], ['jan', '120', '45'], ['feb', '96', '61'], ['mar', '120', '38']].
a) Skriv beste_maned(tabell, kolonne) som returnerer navnet på måneden med høyest verdi i den oppgitte kolonnen. Kolonnen skal finnes ved navn, og månedsnavnet skal hentes fra den første kolonnen.
b) Håndtér at tabellen bare inneholder overskriftsraden: returnér None.
c) Forklar med én setning hva funksjonen din gjør når to måneder har samme høyeste verdi.
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.