Tilbake
7.3

7.3 DRILL — Ledd 2: parsing og datatransformasjon

Gjøre om enkeltstrenger og rader til tall og strukturer — list_from_string, str_to_numbers, tidsstrenger og make_dict — leddet mellom rådata og analyse.

75 min
12 oppgaver
DRILLLedd 2parsingdatatransformasjon
Din fremgang i kapitlet
0 / 12 oppgaver
Forkunnskaper — sist du var her

Ledd 2 bygger på tre ting du allerede har. Her er de, ferdig oppfrisket.

1. Del en linje og konverter et felt (kap. 3.3):

felt = '14.2;85'.split(';')
print(float(felt[0]) + int(felt[1]))

Utskrift:

99.2

2. Heltallsdeling og rest (kap. 1.1) — de to som deler et tall i to biter:

print(197 // 60, 197 % 60)

Utskrift:

3 17

3. Ordboka bygget fra en rad (kap. 3.5):

d = {}
rad = ['R1', 'Elvestien', 14.2]
d[rad[0]] = rad[1:]
print(d)

Utskrift:

{'R1': ['Elvestien', 14.2]}

I tillegg forutsetter kapitlet kap. 7.2, der fila ble lest inn, og kap. 3.4, der formatert utskrift ble gjennomgått.

Løkke 1 — fra felt til tall (~12 min)

Ledd 1 gir deg lister av strenger. Ledd 2 gjør dem om til noe du kan regne med. Det er hele jobben, og den har to standardformer.

Parsing i ledd 2 — fra felt til verdi

Ledd 1 leverte deg rader som ['R1', 'Elvestien', '14.2', '85']. Alle fire elementene er tekst, også de to som ser ut som tall.

Parsing i ledd 2 er å gjøre hvert felt om til den verdien det egentlig representerer: '14.2' blir flyttallet 14.2, '85' blir heltallet 85, '1:04.32' blir 64.32 sekunder.

Testen på om du er ferdig med ledd 2: kan analysefunksjonen i ledd 3 sammenligne, summere og sortere dataene uten å konvertere noe selv? Da er du ferdig.

`tekst_til_liste(s)` — del og konverter i én funksjon

Den vanligste bestillingen i sjanger F: en funksjon som tar én streng og gir tilbake ei liste med tall.

tekst_til_liste('4 9 2 7')  ->  [4, 9, 2, 7]

Blokken over viser hva funksjonen skal gjøre, ikke hvordan.

Oppgaveteksten kaller den ofte list_from_string eller str_to_numbers — engelske navn er like gyldige som norske, og fasitene i dette faget blander. Bruk navnet oppgaven oppgir.

To steg inni: .split(...) for å dele, og int() eller float() på hvert felt.

`til_tall(felt)` — konvertér en hel liste

Motstykket: funksjonen tar imot ei ferdig delt liste og gir tilbake den samme lista med tall i.

Den eksplisitte formen er ei ny liste bygget med append:

ut = []
for x in felt:
    ut.append(float(x))

Blokken over er et utdrag, ikke et program.

Å endre lista på plass (felt[i] = float(felt[i]) i en for i in range(len(felt)):-løkke) er også riktig — men da endrer du samtidig lista til den som kalte deg, og det kan overraske. Bygg heller ei ny liste når oppgaven ikke sier noe annet.

`[float(x) for x in felt]` — snarveien

En listekomprehensjon bygger ei ny liste på én linje: «for hvert x i felt, gi meg float(x)».

tall = [float(x) for x in felt]

Den gjør nøyaktig det samme som løkka med append, og de er sidestilt — begge gir full pott. Fasitene i dette faget bruker snarveier fritt.

Rådet under tidspress er likevel å skrive løkka hvis du er det minste usikker: en listekomprehensjon med en feil i er vanskeligere å rette på papir enn tre linjer med løkke.

✏️Eksempel 1: rundetidene som tall
«Skriv tekst_til_liste(s) som tar en streng med tall skilt av mellomrom og returnerer ei liste med flyttall. Skriv deretter snitt(liste) som returnerer gjennomsnittet.»
Del og konverter:

def tekst_til_liste(s):
    ut = []
    for felt in s.split():
        ut.append(float(felt))
    return ut

print(tekst_til_liste('64.32 58.91 72.07'))

Utskrift:

[64.32, 58.91, 72.07]

Snittet, som gjenbruker den:

def tekst_til_liste(s):
    return [float(felt) for felt in s.split()]

def snitt(liste):
    return sum(liste) / len(liste)

print(snitt(tekst_til_liste('64.32 58.91 72.07')))

Utskrift:

65.1

Legg merke til at svaret har mange desimaler. Det er ikke en feil: 65.10000000000001 er nøyaktig det Python regner ut, fordi flyttall lagres binært og noen desimaltall ikke går opp. Trenger du et pent tall, formaterer du i utskriften:

print('{:.2f}'.format(65.10000000000001))

Utskrift:

65.10

Slik ville sensor sett på det: de to formene av tekst_til_liste er sidestilt. Det som gir uttelling, er at funksjonen returnerer lista og at snitt kaller den i stedet for å dele strengen på nytt.

Kantfeilen å nevne: snitt krasjer med ZeroDivisionError på ei tom liste. Sier ikke oppgaven noe om det, skriver du én setning: «antar at lista har minst ett element». Skal den være robust, er formen if len(liste) == 0: return None — se kap. 7.6.

📝Oppgave 1

(Innstegsoppgave, sjanger B — kodesporing, altså «hva skriver dette programmet ut?».) Hva skriver programmet ut? Skriv begge linjene nøyaktig.

felt = '9.8;430'.split(';')
print(felt[0] + felt[1])
print(float(felt[0]) + int(felt[1]))

📝Oppgave 2
Eksamensnivå, sjanger F
«Skriv til_tall(felt) som tar ei liste av strenger og returnerer ei ny liste der hvert element er gjort om til heltall. Lista som kommer inn, skal ikke endres.»

Skriv funksjonen med en løkke, og vis deretter snarveien med listekomprehensjon.

Løkke 2 — tidsstrengen inn (~14 min)

Tider skrives som tekst i datafiler: '1:04.32' betyr ett minutt, fire sekunder og 32 hundredeler. Du kan ikke sammenligne slike strenger direkte — '0:58.91' og '1:04.32' sammenlignes bokstav for bokstav, og '0' kommer før '1'. Denne gangen gir det tilfeldigvis riktig svar, men bare fordi minuttallet er ett siffer.

Løsningen er alltid den samme: gjør tiden om til ett tall først.

Tidsstrengen `mm:ss.hh`

Formatet du møter oftest i dette faget: minutter, kolon, sekunder, punktum, hundredeler.

'1:04.32' er ett minutt, fire sekunder og 32 hundredeler — til sammen 64.32 sekunder.

Tre ting å legge merke til før du koder:

- Sekunddelen er nullpadd: 4 sekunder skrives 04, ikke 4.
- Det er ett kolon, så .split(':') gir nøyaktig to biter.
- Hundredelene henger sammen med sekundene i den andre biten, og float('04.32') takler dem uten videre.

📜Kodemønster: tidsstreng til sekunder

Tre linjer, og de er de samme hver gang:

deler = tekst.split(':')
minutter = int(deler[0])
sekunder = float(deler[1])
return minutter * 60 + sekunder

Blokken over er mønsteret; den ferdige funksjonen står i eksempel 2.

Har formatet tre ledd ('1:02:05' for time, minutt, sekund), utvides det på samme måte:

timer * 3600 + minutter * 60 + sekunder

Regelen bak: gjør om til den minste enheten, altså sekunder. Da er alle sammenligninger, summeringer og sorteringer rett fram.

`//` og `%` — de to som deler et tall i to biter
a // b gir heltallsdelen av divisjonen, a % b gir resten. De brukes alltid sammen når et tall skal deles i to enheter.

print(197 // 60)
print(197 % 60)

Utskrift:

3
17

197 sekunder er altså 3 minutter og 17 sekunder.

To feller: / gir alltid flyttall, så 197 / 60 er 3.2833… og ikke 3 — det er feilkode #9, blandet heltalls- og flyttallsdeling. Og // runder nedover, også for negative tall: -7 // 2 er -4, ikke -3.

✏️Eksempel 2: rundetidene i svømmestevnet
«Filen stevne.txt har én linje per svømmer: navn, klubb og tid, skilt med semikolon. Tiden er på formen minutter:sekunder.hundredeler. Skriv tid_til_sekunder(tekst) som gir tiden som flyttall, og les_stevne(filnavn) som returnerer en 2D-liste der tiden allerede er gjort om.»

Slik ser fila ut:

Innholdet i stevne.txt:

Ida Nervik;Havorn;1:04.32
Jonas Lie;Delfin;0:58.91
Amina Kaur;Havorn;1:12.07
Petter Ruud;Storm;1:04.09

Omregningen:

def tid_til_sekunder(tekst):
    deler = tekst.split(':')
    minutter = int(deler[0])
    sekunder = float(deler[1])
    return minutter * 60 + sekunder

print(tid_til_sekunder('1:04.32'))
print(tid_til_sekunder('0:58.91'))

Utskrift:

64.32
58.91

Innlesingen som gjenbruker den:

def tid_til_sekunder(tekst):
    deler = tekst.split(':')
    return int(deler[0]) * 60 + float(deler[1])

def les_stevne(filnavn):
    f = open(filnavn, 'r')
    tabell = []
    for linje in f:
        felt = linje.strip().split(';')
        felt[2] = tid_til_sekunder(felt[2])
        tabell.append(felt)
    f.close()
    return tabell

for rad in les_stevne('stevne.txt'):
    print(rad)

Utskrift:

['Ida Nervik', 'Havorn', 64.32]
['Jonas Lie', 'Delfin', 58.91]
['Amina Kaur', 'Havorn', 72.07]
['Petter Ruud', 'Storm', 64.09]

Slik ville sensor sett på det. Deloppgaven med tid_til_sekunder er typisk 5–10 %, og de tre tingene som gir uttelling er: delingen på kolon, * 60 på minuttdelen, og at funksjonen returnerer et tall i stedet for å skrive det ut.

At les_stevne kaller tid_til_sekunder i stedet for å gjenta regnestykket, er det som gir gjenbrukspoengene i den neste deloppgaven.

Flere korrekte løsninger: minutter, sekunder = tekst.split(':') med utpakking rett i tilordningen er like riktig, og kortere. Begge gir full pott.

Kantfeilen: kommer en tid uten kolon ('58.91'), krasjer deler[1] med IndexError. Nevn det i én setning hvis fila kan være slik — her sa oppgaven at formatet er fast.

📝Oppgave 3
Eksamensnivå, sjanger F
«Skriv raskeste(tabell) som tar 2D-lista fra les_stevne og returnerer navnet på svømmeren med lavest tid.»

Tiden ligger i kolonne 2 og er allerede et flyttall.

📝Oppgave 4
Eksamensnivå, sjanger B

Hva skriver programmet ut? Forklar den siste linja med én setning.

tider = ['10:05', '9:30', '11:00']
print(sorted(tider))
print(min(tider))

Løkke 3 — tidsstrengen ut (~12 min)

Analysen din regner i sekunder. Brukeren vil se 00:30:42. Veien tilbake er // og %, brukt to ganger.

— naturlig pausepunkt etter denne løkka —

📜Kodemønster: sekunder til `hh:mm:ss`

Tre uttrykk, alltid i denne rekkefølgen:

timer    = sekunder // 3600
minutter = (sekunder % 3600) // 60
rest     = sekunder % 60

Blokken over er mønsteret; den ferdige funksjonen står i eksempel 3.

Logikken: // 3600 tar ut hele timer, % 3600 gir det som er igjen etter timene, og av det tar // 60 ut hele minutter. % 60 gir sekundene som blir til overs helt til slutt.

Skal du bare ha mm:ss, dropper du første linje og bruker sekunder // 60 og sekunder % 60. Mønsteret er gjennomgått i kap. 3.4; her drilles det i kjedesammenheng.

Nullpadding med `zfill(2)`
str(t).zfill(2) fyller på en null foran til strengen er minst to tegn lang. str(5).zfill(2) gir '05', str(42).zfill(2) gir '42'.

Uten den blir klokkeformatet 0:30:42 skrevet som 0:30:42 i beste fall og 0:3:42 i verste — og eksamensoppgavene ber uttrykkelig om hh:mm:ss med to sifre.

De to andre formene som gir det samme, er '{:02d}'.format(t) og f-strengen f'{t:02d}'. Alle tre er sidestilt. f-strenger er nyere enn arkivet 2014–2019 (verifiser mot gjeldende pensum — varslet inn 2019), så boka viser dem sammen med .format.

Flyttall i tidsutskrift: `'{:05.2f}'`

Skal sekunddelen ha desimaler og nullpadding, gjør '{:05.2f}'.format(x) begge deler: 5 er den totale bredden inkludert punktum og desimaler, .2f er to desimaler.

'{:05.2f}'.format(4.32) gir '04.32' — fem tegn: 0, 4, ., 3, 2.

Det er lett å telle feil her. Skriver du {:02.2f}, ber du om minst to tegn totalt, og siden 4.32 allerede er fire tegn, skjer det ingenting.

✏️Eksempel 3: joggeloggen begge veier
«Filen joggeturer.txt har dato, varighet i hele sekunder og distanse i kilometer, skilt med semikolon. Skriv klokkeformat(sekunder) som gir varigheten på formen hh:mm:ss, og skriv ut hele loggen med varigheten i klokkeformat.»

Slik ser fila ut:

Innholdet i joggeturer.txt:

2026-05-02;1842;7.1
2026-05-05;2711;10.4
2026-05-09;1503;5.8

Omregningen:

def klokkeformat(sekunder):
    timer = sekunder // 3600
    minutter = (sekunder % 3600) // 60
    rest = sekunder % 60
    return str(timer).zfill(2) + ':' + str(minutter).zfill(2) + ':' + str(rest).zfill(2)

print(klokkeformat(1842))
print(klokkeformat(2711))
print(klokkeformat(45))

Utskrift:

00:30:42
00:45:11
00:00:45

Hele loggen, med innlesingen fra kap. 7.2 gjenbrukt:

def klokkeformat(sekunder):
    timer = sekunder // 3600
    minutter = (sekunder % 3600) // 60
    return str(timer).zfill(2) + ':' + str(minutter).zfill(2) + ':' + str(sekunder % 60).zfill(2)

f = open('joggeturer.txt', 'r')
for linje in f:
    felt = linje.strip().split(';')
    print(felt[0], klokkeformat(int(felt[1])), felt[2] + ' km')
f.close()

Utskrift:

2026-05-02 00:30:42 7.1 km
2026-05-05 00:45:11 10.4 km
2026-05-09 00:25:03 5.8 km

Slik ville sensor sett på det: de tre uttrykkene med // og % er selve svaret, og zfill er detaljen som skiller full pott fra nesten. Skriver du str(timer) + ':' + ... uten padding, får du 0:30:42 — riktig regnet, men ikke det formatet oppgaven ba om.

Kontrollregningen (gjør den for hånd, det tar femten sekunder): 1842 sekunder. 1842//3600=01842 // 3600 = 0. 1842%3600=18421842 \% 3600 = 1842, og 1842//60=301842 // 60 = 30. 1842%60=421842 \% 60 = 42. Altså 00:30:42. Stemmer med utskriften.

Kantfeilen å kjenne til: funksjonen forutsetter et heltall. Sender du inn 1842.5, gir // et flyttall, og du får 0.0:30.0:42.5. Konverter med int() først hvis varigheten kan ha desimaler.

📝Oppgave 5
Eksamensnivå, sjanger F
«Skriv sekunder_til_tid(total) som gjør et flyttall med sekunder om til formen minutter:sekunder.hundredeler, altså den motsatte veien av tid_til_sekunder i eksempel 2. Sekunddelen skal alltid ha to sifre foran punktumet og to desimaler.»

Test funksjonen på 64.32 og 58.91.

📝Oppgave 6
Eksamensnivå, sjanger F…

Bruk joggeturer.txt med dato, sekunder og kilometer.

a) Skriv fart(sekunder, km) som returnerer farten i minutter per kilometer, som flyttall.

b) Skriv beste_fart(tabell) som returnerer datoen for turen med lavest tid per kilometer. Tabellen har rader på formen [dato, sekunder, km] med tallene ferdig konvertert.

c) Skriv ut svaret på formen minutter:sekunder per kilometer.

Løkke 4 — felt som ikke er rene (~12 min)

Ikke alle felt er klare til float(). Noen har komma som desimaltegn, noen har en enhet bakpå, og noen er skilt av et vilkårlig antall mellomrom. Alle tre er faste innslag på eksamen, og alle tre løses med ett grep hver.

Varierende antall mellomrom: `.split()`
.split() uten argument deler på blanktegn og slår sammen flere på rad. Det er det eneste riktige valget når kolonnene er justert i stedet for skilt.

linje = '2026-02-01     418   kjeller'
print(linje.split())
print(len(linje.split(' ')))

Utskrift:

['2026-02-01', '418', 'kjeller']
9

Med ett mellomrom som argument blir hvert ekstra mellomrom et eget, tomt felt — ni felt der du ventet tre.

Begrensningen: formen kan ikke brukes når et felt selv inneholder mellomrom, som 'Ida Nervik'. Da må fila ha et ekte skilletegn.

Komma som desimaltegn: `.replace(',', '.')`

Norske datafiler skriver ofte 24,90 der Python krever 24.90. float('24,90') gir en ValueError.

s.replace(gammel, ny) gir en ny streng der alle forekomstene er byttet ut:

print(float('24,90'.replace(',', '.')))

Utskrift:

24.9

Pass på rekkefølgen når fila også bruker komma som skilletegn — da må du dele først og erstatte etterpå, ellers renner feltene sammen. Er begge deler komma, sier oppgaveteksten alltid hvordan den skal leses.

Enhet bakpå: del eller kutt den bort

Et felt som '24,90 kr' har en enhet hengende ved. To rene grep, begge riktige:

- Del på mellomrom og ta første bit: felt.split()[0] gir '24,90'.
- Kutt et fast antall tegn: felt[:-3] fjerner de tre siste tegnene.

Den første er tryggere, fordi den virker enten enheten er kr, km eller kWh. Den andre krasjer stille hvis enheten skifter lengde.

Har feltet et prosenttegn uten mellomrom ('85%'), er .replace('%', '') det korteste grepet.

✏️Eksempel 4: to skitne filer

Les vannmaaler.txt, der kolonnene er justert med et varierende antall mellomrom, og kassalapp.txt, der prisen har både komma og enhet.

Fila med varierende mellomrom:

Innholdet i vannmaaler.txt:

2026-02-01     418   kjeller
2026-03-01       503    kjeller
2026-04-01   477       kjeller

def les_maaler(filnavn):
    f = open(filnavn, 'r')
    tabell = []
    for linje in f:
        felt = linje.split()
        felt[1] = int(felt[1])
        tabell.append(felt)
    f.close()
    return tabell

for rad in les_maaler('vannmaaler.txt'):
    print(rad)

Utskrift:

['2026-02-01', 418, 'kjeller']
['2026-03-01', 503, 'kjeller']
['2026-04-01', 477, 'kjeller']

Merk at .strip() er droppet her: .split() uten argument fjerner linjeskiftet på kjøpet, fordi linjeskift også er et blanktegn. Å ha .strip() med er ikke feil — bare unødvendig.

Fila med komma og enhet:

Innholdet i kassalapp.txt:

melk;2;24,90 kr
kaffe;1;89,50 kr
egg;3;41,00 kr

def les_lapp(filnavn):
    f = open(filnavn, 'r')
    tabell = []
    for linje in f:
        felt = linje.strip().split(';')
        felt[1] = int(felt[1])
        felt[2] = float(felt[2].split()[0].replace(',', '.'))
        tabell.append(felt)
    f.close()
    return tabell

for rad in les_lapp('kassalapp.txt'):
    print(rad)

Utskrift:

['melk', 2, 24.9]
['kaffe', 1, 89.5]
['egg', 3, 41.0]

Den ene tette linja gjør tre ting i rekkefølge: .split()[0] kaster enheten, .replace(',', '.') bytter desimaltegnet, float(...) gjør resten om til tall.

Slik ville sensor sett på det: kjeden av tre kall er lovlig og gir full pott, men den er vanskelig å lese. Deler du den i tre linjer med hvert sitt navn, får du den samme uttellingen og en lettere sjekk:

raa = '24,90 kr'
uten_enhet = raa.split()[0]
med_punktum = uten_enhet.replace(',', '.')
print(float(med_punktum))

Utskrift:

24.9

Kantfeilen: rekkefølgen er ikke likegyldig. float('24,90 kr'.replace(',', '.')) krasjer fortsatt, fordi ' kr' står igjen. Enheten må bort først.

📝Oppgave 7
Eksamensnivå, sjanger F
«Skriv rens_pris(tekst) som gjør en prisstreng som '129,50 kr' om til flyttallet 129.5. Funksjonen skal virke enten enheten står der eller ikke.»

Test på '129,50 kr', '89,50' og '7,00 kr'.

📝Oppgave 8
Eksamensnivå, sjanger B

Hva skriver programmet ut? Forklar forskjellen mellom de to linjene med én setning.

linje = '2026-03-01       503    kjeller'
print(len(linje.split()))
print(len(linje.split(' ')))

Løkke 5 — fra tabell til ordbok (~13 min)

Den siste faste jobben i ledd 2: du har en ferdig 2D-liste, og en senere deloppgave vil slå opp på en nøkkel. Da bygger du ordboka av tabellen — uten å lese fila på nytt.

📜Kodemønster: bygg ordbok fra en 2D-tabell

Fire linjer, og nøkkelkolonnen er det eneste du velger:

d = {}
for rad in tabell:
    d[rad[k]] = rad
return d

Blokken over er mønsteret; den ferdige funksjonen står i eksempel 5.

Tre varianter av verdien, alle riktige, valgt ut fra hva oppgaven skal svare på:

- d[rad[k]] = rad — hele raden, nøkkelen inkludert. Enklest å skrive, ingen forskyvning å huske.
- d[rad[k]] = rad[1:] — resten av raden. Kortere verdi, men indeksene forskyves.
- d[rad[k]] = rad[j] — bare én kolonne, når oppgaven bare spør om én verdi.

Har tabellen en overskriftsrad, må løkka gå over tabell[1:], og nøkkelkolonnen kan finnes med tabell[0].index(navn).

`lag_ordbok(tabell, k)` — nøkkelkolonnen som parameter

Oppgaveteksten kaller den ofte make_dict. Poenget er at nøkkelkolonnen er en parameter, ikke skrevet inn i koden:

lag_ordbok(tabell, 0)  ->  {'R1': [...], 'R2': [...]}
lag_ordbok(tabell, 1)  ->  {'Elvestien': [...], 'Toppturen': [...]}

Blokken over viser to kall, ikke kode som kan kjøres.

Da kan den samme funksjonen brukes to ganger i samme besvarelse med hver sin nøkkel — og det er nøyaktig den slags gjenbruk sensor belønner.

Nøkkelen som ikke er unik

Bygger du {rad[k]: rad} og to rader har samme verdi i kolonne k, forsvinner den første i stillhet.

Sjekken tar én linje, og den er verdt å ta med når du er usikker:

tabell = [['R1', 'Elvestien'], ['R2', 'Toppturen'], ['R1', 'Elvestien nord']]
nokler = []
for rad in tabell:
    nokler.append(rad[0])
print(len(nokler), len(set(nokler)))

Utskrift:

3 2

Er de to tallene forskjellige, er nøkkelen ikke unik — og da skal du enten gruppere ({nøkkel: [rader]}) eller velge en annen kolonne. set(...) gir en samling uten duplikater, og lengden av den er antall forskjellige verdier.

Tomt felt i en rad

Datafiler har hull. En linje som 'R4;;12.0;30' gir ['R4', '', '12.0', '30'] — feltet er tomt, ikke borte.

int('') og float('') krasjer begge med ValueError. Testen står før konverteringen:

if felt[2] == '':
    felt[2] = 0
else:
    felt[2] = float(felt[2])

Blokken over er et utdrag fra innlesingsløkka.

Hva du setter inn i stedet, er en antakelse du skal skrive ned: 0 hvis det betyr «ingen», None hvis det betyr «ukjent». De to er ikke det samme, og valget kan endre svaret på analysen.

✏️Eksempel 5: sykkelrutene som ordbok, to ganger
«Filen sykkelruter.txt har overskriftsraden rutenr;navn;lengde;stigning. Skriv lag_ordbok(tabell, k) som bygger en ordbok med kolonne k som nøkkel og hele raden som verdi. Bruk den til å slå opp både på rutenummer og på rutenavn.»

Slik ser fila ut:

Innholdet i sykkelruter.txt:

rutenr;navn;lengde;stigning
R1;Elvestien;14.2;85
R2;Toppturen;9.8;430
R3;Havnerunden;6.5;20

Innlesing og ordbokbygging, med overskriftsraden hoppet over i ordboka:

def les_ruter(filnavn):
    f = open(filnavn, 'r')
    tabell = [f.readline().strip().split(';')]
    for linje in f:
        felt = linje.strip().split(';')
        felt[2] = float(felt[2])
        felt[3] = int(felt[3])
        tabell.append(felt)
    f.close()
    return tabell

def lag_ordbok(tabell, k):
    d = {}
    for rad in tabell[1:]:
        d[rad[k]] = rad
    return d

tabell = les_ruter('sykkelruter.txt')
paa_nummer = lag_ordbok(tabell, 0)
paa_navn = lag_ordbok(tabell, tabell[0].index('navn'))
print(paa_nummer['R2'])
print(paa_navn['Havnerunden'])
print(len(paa_navn))

Utskrift:

['R2', 'Toppturen', 9.8, 430]
['R3', 'Havnerunden', 6.5, 20]
3

Slik ville sensor sett på det: at lag_ordbok tar k som parameter er det som gjør at den kan kalles to ganger. En besvarelse med lag_ordbok_paa_nummer og lag_ordbok_paa_navn — to nesten like funksjoner — gjør den samme jobben, men mister gjenbrukspoengene.

Legg merke til at det andre kallet finner kolonnen med tabell[0].index('navn') i stedet for tallet 1. Det er det samme fullscore-grepet som i kap. 7.2: koden overlever en omstokket kolonnerekkefølge.

Kantfeilen: paa_navn['Havnerunda'] — med skrivefeil — gir KeyError. Skal oppslaget tåle en ukjent nøkkel, bruker du paa_navn.get('Havnerunda', None), som gir None i stedet for å krasje.

📝Oppgave 9
Eksamensnivå, sjanger F
«Du har 2D-lista fra stevne.txt med rader på formen [navn, klubb, tid], uten overskriftsrad. Skriv tider_per_klubb(tabell) som returnerer {klubb: [tid, tid, …]} med alle tidene til hver klubb.»
📝Oppgave 10
Eksamensnivå, sjanger F

En fil har rader på formen rutenr;navn;lengde;stigning, men noen rader mangler stigningen — feltet er tomt.

a) Skriv les_robust(filnavn) som leser fila til en 2D-liste og setter stigningen til None når feltet er tomt.

b) Skriv snitt_stigning(tabell) som regner gjennomsnittlig stigning bare over radene der stigningen er kjent.

c) Skriv én setning om antakelsen du har gjort.

Løkke 6 — parseleddet i kjeden (~12 min)

Til slutt: hvor hører parsefunksjonene hjemme? Svaret avgjør om du får gjenbrukspoengene eller ikke.

Parsefunksjonen er en hjelpefunksjon
tid_til_sekunder, rens_pris og til_tall skal stå som egne funksjoner og kalles fra innlesingen — ikke skrives rett inn i innlesingsløkka.

Tre grunner, alle poenggivende:

- Oppgaven ber som regel om dem som egne deloppgaver med egne prosenter.
- De blir testbare: du kan kalle tid_til_sekunder('1:04.32') alene og se at svaret er 64.32.
- De blir gjenbrukbare: den samme funksjonen brukes i innlesingen, i utskriften og i menyen.

Regelen i én setning: én funksjon gjør én ting, og innlesingen kaller den.

✏️Eksempel 6: kjeden med parseleddet på plass

Sett sammen innlesingen fra kap. 7.2 og parsefunksjonen fra eksempel 2, og legg til en utskrift som viser tidene tilbake i tidsformat. Hver funksjon i sin egen blokk, som deloppgavene på et sett.

Deloppgave a) — parsefunksjonen begge veier. To små funksjoner, ingen filer:

def tid_til_sekunder(tekst):
    deler = tekst.split(':')
    return int(deler[0]) * 60 + float(deler[1])

def sekunder_til_tid(total):
    minutter = int(total // 60)
    return str(minutter) + ':' + '{:05.2f}'.format(total - minutter * 60)

print(tid_til_sekunder('1:12.07'))
print(sekunder_til_tid(72.07))

Utskrift:

72.07
1:12.07

Deloppgave b) — innlesingen som kaller a):

def les_stevne(filnavn):
    f = open(filnavn, 'r')
    tabell = []
    for linje in f:
        felt = linje.strip().split(';')
        felt[2] = tid_til_sekunder(felt[2])
        tabell.append(felt)
    f.close()
    return tabell

Deloppgave c) — utskriften som kaller a) den andre veien:

def skriv_resultatliste(tabell):
    for rad in tabell:
        print(rad[0].ljust(14) + rad[1].ljust(10) + sekunder_til_tid(rad[2]))

Utskrift: ingen — blokken definerer bare funksjonen. Den skriver først noe når den blir kalt.

Deloppgave d) — main, som leser fila én gang:

def main(filnavn):
    tabell = les_stevne(filnavn)
    skriv_resultatliste(tabell)

Kaller du main('stevne.txt') etter at de fire funksjonene over er definert, skriver programmet:

Utskrift:

Ida Nervik    Havorn    1:04.32
Jonas Lie     Delfin    0:58.91
Amina Kaur    Havorn    1:12.07
Petter Ruud   Storm     1:04.09

Slik ville sensor sett på det: tid_til_sekunder kalles fra innlesingen, sekunder_til_tid fra utskriften, og ingen av de to regnestykkene står skrevet mer enn ett sted. Det er gjenbruk i praksis, og det er verdt poeng i hver eneste deloppgave som bruker det.

Legg også merke til at tabellen inneholder tall, ikke tidsstrenger, hele veien fra innlesingen til utskriften. Konverteringen tilbake til tekst skjer først i det siste øyeblikket, når noe skal vises for et menneske. Det er en god regel i alle programmer: regn med tall, vis tekst.

📝Oppgave 11
Eksamensnivå, sjanger F
«Utvid kjeden i eksempel 6: skriv differanse(tabell) som returnerer differansen mellom den raskeste og den treigeste tiden, som en tidsstreng på formen minutter:sekunder.hundredeler

Funksjonen skal gjenbruke sekunder_til_tid.

📝Oppgave 12
Eksamensnivå, sjanger E…
«Filen maalinger.log har én linje per avlesning, med feltene skilt av et varierende antall mellomrom: dato, forbruk i liter og målerplassering. Forbruket kan være tomt når avlesningen mislyktes.

(a) Skriv les_logg(filnavn) som returnerer en 2D-liste der forbruket er heltall, eller None når feltet mangler.

(b) Skriv forbruk_per_maaned(tabell) som returnerer {maaned: sum liter}, der måneden er de sju første tegnene i datoen ('2026-02'). Avlesninger uten forbruk skal ikke telle med.»

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.