Tilbake
3.2

3.2 2D-lister (tabeller)

Liste av lister som en tabell med rader og kolonner — den sentrale datastrukturen i programmeringsoppgaven, inkludert dynamisk kolonneoppslag.

50 min
5 oppgaver
2D-lister (tabeller)
Din fremgang i kapitlet
0 / 5 oppgaver
Forkunnskaper

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

2D-liste (tabell)

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.

`tabell[i][j]`

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.

`tabell.append(rad)`

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.

`len(tabell)` — antall rader

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.

`len(tabell[0])` — antall kolonner

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.

✏️Eksempel 1: fire værstasjoner som tabell

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 3

Legg 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.

📝Oppgave 1

(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 nabo­rader, eller endre celler.

`for rad in tabell:`

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:].

`for i in range(len(tabell)):`

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).

Nøstet løkke over alle cellene

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.

Overskriftsraden

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.

✏️Eksempel 2: skriv ut hver stasjon med nedbøren sin

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 mm

To 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.

📝Oppgave 2
Eksamensnivå, sjanger B

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.

Kolonneuttrekk med løkke

Å 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.

`[rad[k] for rad in tabell]` — listekomprehensjon

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.

`tabell[0].index(navn)` — dynamisk kolonneindeks

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.

✏️Eksempel 3: hent en kolonne — tre veier, én av dem robust

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.

📝Oppgave 3
Eksamensnivå, sjanger G

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.

Transponering

Å 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.

`tabell[i][j] = v` — endre én celle

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.

Ujevn tabell

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.

Kopi av en tabell
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.

✏️Eksempel 4: transponer en tabell

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 3

Tre 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.

📝Oppgave 4
Eksamensnivå, sjanger B

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])

📝Oppgave 5
Eksamensnivå, sjanger G

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.

Repetisjon — de seks setningene du bør ha med deg
API- og konstruksjonsliste

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.