Tilbake
7.2

7.2 DRILL — Ledd 1: les fil → 2D-tabell/ordbok

Full drill på det første leddet: fra tekstfil til ferdig 2D-tabell eller ordbok, med riktig separator, strip og typekonvertering, over roterende domener.

75 min
12 oppgaver
DRILLLedd 1les fil → 2D-tabell/ordbok
Din fremgang i kapitlet
0 / 12 oppgaver
Forkunnskaper — sist du var her

Alt du trenger, er gjennomgått i kap. 4.1. Her er de tre grepene, ferdig oppfrisket, så du kan drille uten å bla.

1. Åpne, gå gjennom, lukke:

f = open('kaker.txt', 'r')
for linje in f:
    print(linje.strip())
f.close()

Utskrift:

kanelsnurr;38;24
skolebrod;25;29
solskinnsbolle;12;26

2. Rens først, del så — rekkefølgen er fast:

linje = 'kanelsnurr;38;24\n'
print(linje.strip().split(';'))

Utskrift:

['kanelsnurr', '38', '24']

3. Konverter tallfeltene med int() eller float() med én gang:

felt = ['kanelsnurr', '38', '24']
felt[1] = int(felt[1])
print(felt)

Utskrift:

['kanelsnurr', 38, '24']

I tillegg bygger kapitlet på kap. 3.2 (2D-lister), kap. 3.5 (ordbøker) og kap. 7.1 (kjeden som helhet).

Løkke 1 — de seks stegene (~12 min)

Innlesingsfunksjonen er den samme hver gang. Lær den som seks steg, ikke som en tekst du husker deler av.

📜Løsningsoppskrift — ledd 1: fra fil til datastruktur

Seks steg. Skriv dem ned på kladdearket før du koder, hver eneste gang.

1. Åpne fila. f = open(filnavn, 'r') — eller with open(filnavn) as f:, som lukker fila for deg.
2. Hopp over overskriftsraden hvis fila har en: overskrift = f.readline().strip().split(sep) før løkka.
3. Gå gjennom resten linje for linje: for linje in f:.
4. Rens og del: felt = linje.strip().split(sep)strip() alltid før split().
5. Konverter tallfeltene med int() eller float(), og legg raden i strukturen: tabell.append(felt) for en 2D-liste, eller d[felt[0]] = felt[1:] for en ordbok.
6. Lukk og returner: f.close() etter løkka, deretter return tabell.

Bare tre ting varierer mellom oppgavene: skilletegnet i steg 4, om fila har overskriftsrad i steg 2, og om du bygger tabell eller ordbok i steg 5. Alt annet er identisk.

`with open(filnavn) as f:`

En sidestilt form for steg 1 som lukker fila for deg når blokken er ferdig — også hvis programmet krasjer underveis.

with open('kaker.txt', 'r') as f:
    for linje in f:
        felt = linje.strip().split(';')

Alt som skal skje med fila, står inne i with-blokken, og du skriver ingen close(). Det er ikke mulig å glemme lukkingen.

Denne boka bruker open/close som hovedform, fordi det er formen de offisielle løsningsforslagene bruker. Begge gir full pott — velg den du skriver raskest og riktigst for hånd.

Filnavnet som parameter

Innlesingsfunksjonen tar filnavnet inn som parameter, aldri skrevet rett inn i koden:

def les_tabell(filnavn):
    f = open(filnavn, 'r')

Tre grunner, og alle tre er poenggivende: oppgaveteksten oppgir som regel signaturen med parameteren, sensor kan teste funksjonen på en annen fil, og senere deloppgaver kan lese to filer med den samme funksjonen.

Motstykket — open('ladestasjoner.txt', 'r') inne i funksjonen — binder funksjonen til én bestemt fil og gjør den ubrukelig i resten av kjeden.

Returverdien er strukturen, ikke utskriften

Innlesingsfunksjonen skal returnere tabellen eller ordboka. Den skal ikke skrive den ut.

print inne i innlesingsfunksjonen er en av de vanligste tidstyvene: det ser ut som om funksjonen virker, men den som kaller den, får None tilbake og har ingenting å analysere.

Regelen i én setning: funksjonen som leser, returnerer data; funksjonen som skriver ut, tar data inn. Er du usikker underveis mens du koder, skriv ut resultatet av kallet i stedet: print(les_tabell('kaker.txt')).

✏️Eksempel 1: ladestasjonene, hele deloppgave a)
«Filen ladestasjoner.txt har en overskriftsrad og deretter én linje per ladestasjon: navn, kommune, antall uttak og pris per kilowattime, skilt med semikolon. Skriv les_stasjoner(filnavn) som returnerer en 2D-liste der overskriftsraden er rad 0, antall uttak er heltall og prisen er flyttall.»

Slik ser fila ut:

Innholdet i ladestasjoner.txt:

navn;kommune;uttak;pris
Storsenteret;Bodo;12;4.90
Fjordbrygga;Bodo;4;5.25
Nordparken;Fauske;8;4.50
Rutebilstasjonen;Fauske;6;5.10

De seks stegene, rett ned:

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

for rad in les_stasjoner('ladestasjoner.txt'):
    print(rad)

Utskrift:

['navn', 'kommune', 'uttak', 'pris']
['Storsenteret', 'Bodo', 12, 4.9]
['Fjordbrygga', 'Bodo', 4, 5.25]
['Nordparken', 'Fauske', 8, 4.5]
['Rutebilstasjonen', 'Fauske', 6, 5.1]

Legg merke til at prisene skrives 4.9 og ikke 4.90. Det er ikke en feil — float husker tallverdien, ikke hvor mange desimaler den var skrevet med. Trenger du to desimaler i utskriften, formaterer du der: '{:.2f}'.format(4.9).

Slik ville sensor sett på det. Deloppgaven er verdt rundt 10 %, og de fem tingene som gir uttelling hver for seg er: filnavnet som parameter, overskriftsraden lest med readline() før løkka, strip() før split(';'), konverteringen av begge tallkolonnene, og return av tabellen.

Flere korrekte løsninger. with open(filnavn) as f: uten close() er like riktig. Å legge overskriftsraden i en egen returverdi (return overskrift, tabell) er også riktig, så lenge du sier fra i en kommentar — men les oppgaveteksten: her ba den eksplisitt om overskriften som rad 0.

Og en kantfeil som ikke koster mye: hadde du glemt float() på priskolonnen, ville koden fortsatt kjørt, og strukturen vært riktig. Det er en typefeil i ett felt, ikke en strukturfeil — sensor trekker for feltet, ikke for oppgaven.

📝Oppgave 1

(Innstegsoppgave, sjanger B — kodesporing, altså «hva skriver dette programmet ut?».) Hva skriver programmet ut? Skriv de to linjene nøyaktig slik terminalen viser dem.

linje = 'Nordparken;Fauske;8;4.50\n'
felt = linje.split(';')
print(felt)
print(len(felt))

📝Oppgave 2
Sjanger E
«Filen kaker.txt har ingen overskriftsrad. Hver linje har kakens navn, antall solgte og pris i kroner, skilt med semikolon. Skriv les_kaker(filnavn) som returnerer en 2D-liste der begge tallene er heltall.»

Løkke 2 — skilletegnet (~12 min)

Steg 4 er det eneste steget der du kan ta helt feil uten at programmet sier fra. Deler du på feil tegn, får du én lang bit i stedet for fire felt — og feilen dukker først opp tre deloppgaver senere.

Skilletegnet leses ut av fila, ikke ut av vanen
Skilletegnet (separatoren) er tegnet som skiller feltene i en linje. Det er en del av dataformatet, og oppgaveteksten sier alltid hvilket det er — eller viser deg fila.

De fire du møter er semikolon (';'), komma (','), tabulator ('\t', ett tabulatortegn) og mellomrom.

Bruk to sekunder på å se på fila før du skriver .split(...). Det er den billigste kontrollen i hele faget: feil skilletegn er feilkode #3, og den koster hele deloppgaven fordi ingenting etterpå får tak i feltene sine.

Tabulator som skilletegn: `.split('\t')`

En tabulator er ett enkelt tegn som ser ut som et langt mellomrom. I Python skrives det '\t' — en bakstrek og en t, altså to tegn i kildekoden, men ett tegn i strengen.

felt = linje.strip().split('\t')

Blokken over er ett utdrag av en løkke, ikke et program som kan kjøres for seg.

Kjenner du den igjen i fila? Kolonnene ser ut til å stå pent under hverandre uten at noen har telt mellomrom. Prøver du .split(' ') på en tabulatorfil, får du ett felt tilbake — for det finnes ingen mellomrom å dele på.

`.split()` uten argument

Deler på blanktegn av alle slag — mellomrom, tabulator, linjeskift — og slår sammen flere på rad til ett skille. Tomme felt forsvinner helt.

print('Vera    6.0   mandag'.split())

Utskrift:

['Vera', '6.0', 'mandag']

Det gjør formen perfekt når kolonnene er justert med et varierende antall mellomrom, som i utskrifter fra eldre systemer.

To ting å vite: den renser linjeskiftet på kjøpet, så .strip() er valgfritt akkurat her — og den kan ikke brukes på felt som selv inneholder mellomrom, som 'Sigrid Aas'. Der ville navnet blitt delt i to.

✏️Eksempel 2: kantinesalget, med tabulator
«Filen kantinesalg.txt har ingen overskriftsrad. Hver linje har varenavn, antall solgt og pris, skilt med tabulator. Skriv les_salg(filnavn) som returnerer en 2D-liste med antallet som heltall og prisen som flyttall.»

Slik ser fila ut. Kolonnene står pent, men mellom dem står det ett tegn, ikke fem mellomrom:

Innholdet i kantinesalg.txt:

kaffe	34	22.00
vaffel	19	28.00
suppe	11	65.00

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

print(les_salg('kantinesalg.txt'))

Utskrift:

[['kaffe', 34, 22.0], ['vaffel', 19, 28.0], ['suppe', 11, 65.0]]

Kontrollen som avslører feil skilletegn på to sekunder: skriv ut len(felt) for den første linja. Får du 1, delte du på feil tegn — hele linja ble liggende som ett felt.

linje = 'kaffe\t34\t22.00'
print(len(linje.split(' ')))
print(len(linje.split('\t')))

Utskrift:

1
3

Slik ville sensor sett på det: her er '\t' hele forskjellen mellom full pott og null på deloppgaven. Er du i tvil på eksamen, skriver du .split() uten argument i stedet — den takler både tabulator og mellomrom, og gir riktig resultat på denne fila fordi ingen av feltene har mellomrom i seg. Skriv én setning om at du antok det.

📝Oppgave 3
Eksamensnivå, sjanger E
«Filen pantelevering.txt har ingen overskriftsrad, og feltene er skilt med komma: butikknavn, dato og antall flasker. Skriv les_panting(filnavn) som returnerer en 2D-liste der flasketallet er heltall.»

Innholdet i pantelevering.txt:

Naerbutikken,2026-03-04,148
Torgkiosken,2026-03-04,63
Naerbutikken,2026-03-05,201

Skriv funksjonen, og skriv i tillegg én setning om hva som ville skjedd hvis du hadde brukt semikolon som skilletegn.

📝Oppgave 4
Eksamensnivå, sjanger B

Fila kantinesalg.txt har tabulator mellom feltene. Hva skriver dette programmet ut?

f = open('kantinesalg.txt', 'r')
linje = f.readline()
f.close()
a = linje.strip().split(';')
b = linje.strip().split()
print(len(a), len(b))
print(a)

Løkke 3 — overskriftsraden (~12 min)

Omtrent halvparten av filene på eksamen har en overskriftsrad øverst. Den er både et problem og en gave: et problem fordi den ikke er data, og en gave fordi den lar deg finne kolonnene ved navn i stedet for å telle.

— naturlig pausepunkt etter denne løkka —

Overskriftsraden — tre måter å håndtere den på

Alle tre er riktige, og valget styres av hva resten av oppgaven skal gjøre:

1. Ta den vare på som rad 0: tabell = [overskrift]. Da kan analysefunksjonene finne kolonnene med tabell[0].index('pris'). Analysen må huske tabell[1:].
2. Returner den for seg: return overskrift, tabell. Ryddig, men den som kaller, må pakke ut to verdier.
3. Kast den: f.readline() uten å ta vare på resultatet. Enklest — men da må kolonnenumrene hardkodes senere, og det bryter et av fullscore-kravene.

Denne boka bruker måte 1 når ingenting annet er sagt, fordi den gir robuste analysefunksjoner uten ekstra returverdier. Les alltid oppgaveteksten: sier den «returnerer en 2D-liste med målingene», mener den som regel uten overskriftsraden.

`f.readline()` foran løkka
f.readline() leser og forbruker én linje. Kaller du den før for linje in f:, starter løkka på linje 2.

f = open('ladestasjoner.txt', 'r')
overskrift = f.readline().strip().split(';')
for linje in f:
    pass
f.close()

Filobjektet husker hvor langt du har lest — det er hele mekanismen. Derfor virker dette bare før løkka. Kaller du readline() inne i løkka, hopper du over annenhver linje.

Alternativene er f.readlines()[1:], som leser hele fila til ei liste og hopper over det første elementet, og en teller i løkka. Begge er riktige; readline() foran løkka er den korteste.

Overskriftsraden som rad 0

Når overskriften er rad 0, er tabellen din selvbeskrivende: den bærer med seg navnet på hver kolonne.

Det gir to grep resten av kjeden lever av:

- k = tabell[0].index('pris') — kolonnenummeret funnet ved navn. Kommer kolonnene i en annen rekkefølge neste år, virker koden fortsatt.
- tabell[1:] — alle datalinjene, uten overskriften.

Prisen du betaler, er at du må huske [1:] i hver analysefunksjon. Glemmer du det, prøver koden å regne på teksten 'pris' og gir en TypeError eller ValueError.

✏️Eksempel 3: samme fil, tre håndteringer av overskriften

Les ladestasjoner.txt på alle tre måtene og sammenlign hva den som kaller funksjonen får tilbake.

Måte 1 — overskriften som rad 0. Dette er formen resten av boka bruker:

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

t = les_med_overskrift('ladestasjoner.txt')
print(t[0])
print(len(t))

Utskrift:

['navn', 'kommune', 'uttak', 'pris']
5

Fem rader: én overskrift og fire stasjoner.

Måte 2 — to returverdier:

def les_todelt(filnavn):
    f = open(filnavn, 'r')
    overskrift = f.readline().strip().split(';')
    tabell = []
    for linje in f:
        tabell.append(linje.strip().split(';'))
    f.close()
    return overskrift, tabell

o, t = les_todelt('ladestasjoner.txt')
print(o)
print(len(t))

Utskrift:

['navn', 'kommune', 'uttak', 'pris']
4

Måte 3 — kast overskriften:

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

t = les_uten_overskrift('ladestasjoner.txt')
print(len(t))
print(t[0])

Utskrift:

4
['Storsenteret', 'Bodo', '12', '4.90']

Slik ville sensor sett på det: alle tre gir full pott på deloppgave a). Forskjellen dukker opp i deloppgave c), analysen. Med måte 1 og 2 kan du skrive overskrift.index('pris'); med måte 3 må du skrive rad[3], og da har du hardkodet kolonnerekkefølgen — som er feilkode #4 og et uttalt fullscore-krav.

Velg måte 3 bare når oppgaven eksplisitt sier at fila ikke har overskrift, eller når den oppgir kolonnerekkefølgen som gitt.

📝Oppgave 5
Eksamensnivå, sjanger E
«Filen medlemmer.txt har en overskriftsrad: medlemsnr;navn;klubb;poeng. Skriv les_medlemmer(filnavn) som returnerer en 2D-liste med overskriftsraden som rad 0, og med medlemsnummer og poeng som heltall.»

Innholdet i medlemmer.txt:

medlemsnr;navn;klubb;poeng
104;Sigrid Aas;Klatreklubben;312
118;Mo Amin;Turlaget;287
127;Elin Berg;Klatreklubben;295

Skriv funksjonen, og vis i tillegg med én kodelinje hvordan en analysefunksjon ville funnet poengkolonnen etterpå.

📝Oppgave 6
Eksamensnivå, sjanger B

Fila ladestasjoner.txt har en overskriftsrad. Hva skriver dette programmet ut, og hvorfor er svaret ikke 4?

f = open('ladestasjoner.txt', 'r')
antall = 0
for linje in f:
    antall = antall + 1
f.close()
print(antall)

Løkke 4 — konverteringen (~12 min)

Steg 5 er stedet der de fleste stumme feilene oppstår. Tallene ser riktige ut i utskriften enten de er tekst eller tall — forskjellen viser seg først når du sammenligner eller regner.

Konverter under innlesingen, ikke etterpå

Legg int() og float() inn i innlesingsløkka, mens du likevel har raden i hånda:

felt[2] = int(felt[2])
felt[3] = float(felt[3])

De to linjene over er et utdrag fra innlesingsløkka, ikke et program for seg.

Da er tabellen ferdig regnbar når den forlater funksjonen, og ingen av de fire–fem analysefunksjonene etterpå trenger å tenke på det.

Alternativet — å konvertere inne i hver analysefunksjon (int(rad[2]) > int(beste[2])) — virker også, men du må huske det hver eneste gang, og det er nettopp der feilkode #1 slår til. Ett sted er tryggere enn fem.

Hvilken av dem: int() for heltall (antall, årstall, poeng), float() for alt med desimaler (pris, temperatur, kilo). Er du i tvil, bruk float() — den takler '12' like godt som '12.5', mens int('12.5') krasjer med en ValueError.

Konverter bare tallkolonnene

Navn, kommuner, datoer og klubbnavn skal bli stående som tekst. int('Bodo') krasjer, og float('2026-03-04') gjør det også.

Vet du kolonnenumrene, skriver du dem rett ut. Er tabellen bred, eller kan rekkefølgen variere, finner du dem ved navn først:

k_uttak = overskrift.index('uttak')
felt[k_uttak] = int(felt[k_uttak])

Utdrag igjen: den første linja står før løkka, den andre inne i den.

Datoer er verdt en egen merknad: de ser ut som tall, men '2026-03-04' er tekst og skal forbli tekst. Sortering på en datostreng skrevet slik virker likevel riktig, fordi år–måned–dag sorterer likt som tekst og som dato.

✏️Eksempel 4: konverter kolonnene funnet ved navn
«Filen medlemmer.txt har en overskriftsrad, men kolonnene kan komme i hvilken som helst rekkefølge. Skriv les_robust(filnavn) som leser fila til en 2D-liste og konverterer kolonnene medlemsnr og poeng til heltall — uansett hvor de står.»

Grepet er å slå opp kolonnenumrene én gang, rett etter at overskriften er lest, og deretter bruke dem i løkka:

def les_robust(filnavn):
    f = open(filnavn, 'r')
    overskrift = f.readline().strip().split(';')
    k_nr = overskrift.index('medlemsnr')
    k_poeng = overskrift.index('poeng')
    tabell = [overskrift]
    for linje in f:
        felt = linje.strip().split(';')
        felt[k_nr] = int(felt[k_nr])
        felt[k_poeng] = int(felt[k_poeng])
        tabell.append(felt)
    f.close()
    return tabell

tabell = les_robust('medlemmer.txt')
print(tabell[1])
print(tabell[0].index('poeng'))

Utskrift:

[104, 'Sigrid Aas', 'Klatreklubben', 312]
3

Hvorfor oppslaget står utenfor løkka: .index(...) går gjennom overskriftsraden på nytt hver gang den kalles. Står den inne i løkka, gjør programmet den samme jobben én gang per linje. På fire linjer merkes det ikke, men det er en vane verdt å ha — og koden blir kortere.

Slik ville sensor sett på det: dette er selve fullscore-formen for deloppgave a). Den koster to ekstra linjer og gjør funksjonen immun mot en omstokket kolonnerekkefølge — som er nøyaktig det robusthetskravet i kap. 7.6 handler om.

Kantfeilen å kjenne til: finnes ikke kolonnenavnet, krasjer .index(...) med ValueError: 'poeng' is not in list. Er du usikker på om kolonnen finnes, kan du teste med if 'poeng' in overskrift: først.

📝Oppgave 7
Eksamensnivå, sjanger E…
«Filen pantelevering.txt har feltene butikk, dato og antall flasker, skilt med komma og uten overskriftsrad. Skriv les_med_pant(filnavn) som returnerer en 2D-liste der hver rad har fått en fjerde kolonne: pantebeløpet, som er 2 kroner per flaske.»

Beløpet skal være et heltall.

📝Oppgave 8
Eksamensnivå, sjanger E
«Filen ladestasjoner.txt har en overskriftsrad, men du vet ikke hvilken rekkefølge kolonnene kommer i. Skriv les_priser(filnavn) som returnerer en ordbok {stasjonsnavn: pris} med prisen som flyttall.»

Du skal ikke anta at navn er første kolonne eller at pris er den siste.

Løkke 5 — ordboksvarianten (~15 min)

Halvparten av oppgavene ber om en 2D-liste, den andre halvparten om en ordbok. Stegene er de samme; bare steg 5 skifter én linje.

Fil til ordbok: velg nøkkelkolonnen først

Ordboka trenger to ting av hver linje: hva som skal være nøkkel, og hva som skal være verdi.

Oppgaveteksten sier det nesten alltid rett ut: «returnerer en ordbok med medlemsnummer som nøkkel». Sier den det ikke, velger du den kolonnen som er unik og som senere spørsmål slår opp på — navn, nummer, kode.

Selve linja i steg 5 blir da én av disse tre:

- d[felt[0]] = felt[1] — én verdi per nøkkel
- d[felt[0]] = felt[1:] — resten av raden som liste
- d[felt[0]] = felt — hele raden, nøkkelkolonnen inkludert

`d[felt[0]] = felt[1:]`

Nøkkelen er første felt, verdien er resten av raden som ei liste. felt[1:] er et utsnitt fra indeks 1 og ut, og gir alltid ei ny liste — også når det bare er ett felt igjen.

felt = ['104', 'Sigrid Aas', 'Klatreklubben', '312']
d = {}
d[felt[0]] = felt[1:]

Formen er den vanligste i eksamensfasitene, fordi den gir kort kode og fordi oppslaget etterpå er lesbart: d['104'][0] er navnet, d['104'][2] er poengsummen.

Prisen er at indeksene i verdilista er forskjøvet én plass i forhold til fila. Skriv det i en kommentar hvis raden er bred — det er lett å telle feil under tidspress.

Når nøkkelen ikke er unik

Setter du inn den samme nøkkelen to ganger, forsvinner den første verdien uten et pip. Det er ikke en feil i Python — en ordbok har per definisjon én verdi per nøkkel.

Er nøkkelen ikke unik, er det som regel fordi oppgaven egentlig ber om noe annet:

- Skal verdiene legges sammen, er det telleren: d[k] = d.get(k, 0) + tall.
- Skal alle radene tas vare på, er det grupperingen: if k not in d: d[k] = [] og deretter d[k].append(rad).

Spørsmålet som avgjør: «hvor mange til sammen» peker på telleren, «alle som …» peker på grupperingen, «hva er verdien for X» peker på det enkle oppslaget.

✏️Eksempel 5: samme fil, tre ordbøker

Bygg tre forskjellige ordbøker av medlemmer.txt: én med hele raden som verdi, én som teller medlemmer per klubb, og én som grupperer navnene per klubb.

Ordbok 1 — oppslag på medlemsnummer. Nøkkelen er unik, så det enkle mønsteret holder:

def les_til_ordbok(filnavn):
    f = open(filnavn, 'r')
    f.readline()
    d = {}
    for linje in f:
        felt = linje.strip().split(';')
        felt[3] = int(felt[3])
        d[felt[0]] = felt[1:]
    f.close()
    return d

d = les_til_ordbok('medlemmer.txt')
print(d['118'])
print(len(d))

Utskrift:

['Mo Amin', 'Turlaget', 287]
3

Merk forskyvningen: poengsummen lå i kolonne 3 i fila, men ligger på indeks 2 i verdilista.

Ordbok 2 — teller per klubb. Nøkkelen gjentar seg, og verdien skal legges sammen:

def antall_per_klubb(filnavn):
    f = open(filnavn, 'r')
    f.readline()
    d = {}
    for linje in f:
        felt = linje.strip().split(';')
        klubb = felt[2]
        d[klubb] = d.get(klubb, 0) + 1
    f.close()
    return d

print(antall_per_klubb('medlemmer.txt'))

Utskrift:

{'Klatreklubben': 2, 'Turlaget': 1}

Ordbok 3 — gruppering. Nøkkelen gjentar seg, og alle radene skal tas vare på:

def navn_per_klubb(filnavn):
    f = open(filnavn, 'r')
    f.readline()
    d = {}
    for linje in f:
        felt = linje.strip().split(';')
        klubb = felt[2]
        if klubb not in d:
            d[klubb] = []
        d[klubb].append(felt[1])
    f.close()
    return d

print(navn_per_klubb('medlemmer.txt'))

Utskrift:

{'Klatreklubben': ['Sigrid Aas', 'Elin Berg'], 'Turlaget': ['Mo Amin']}

Slik ville sensor sett på det: de tre er ikke tre vanskelighetsgrader, men tre svar på tre forskjellige spørsmål. Velger du gruppering der oppgaven ba om et oppslag, får du riktig kode på feil oppgave — og det gir lite uttelling selv om koden virker.

Les returtypen i oppgaveteksten en ekstra gang. {klubb: antall} og {klubb: [navn]} er ulike bestillinger, og forskjellen er synlig i klammene.

Én ting til: her leser hver funksjon fila selv, fordi de er tre uavhengige eksempler. I en ekte besvarelse ville du lest fila én gang i main og gitt tabellen til alle tre — se kap. 7.1.

📝Oppgave 9
Eksamensnivå, sjanger E
«Filen kaker.txt har ingen overskriftsrad: kakens navn, antall solgte og pris, skilt med semikolon. Skriv les_kakeordbok(filnavn) som returnerer {kakenavn: [antall, pris]} med begge tallene som heltall.»
📝Oppgave 10
Eksamensnivå, sjanger E…
«Filen pantelevering.txt har butikk, dato og antall flasker, skilt med komma. Skriv leveringer_per_butikk(filnavn) som returnerer {butikk: [antall1, antall2, …]} — alle flasketallene butikken har levert, som heltall, i den rekkefølgen de står i fila.»

Den samme butikken kan stå på flere linjer.

Løkke 6 — filer som ikke er helt rene (~12 min)

Datafiler i virkeligheten har tomme linjer, kommentarlinjer og mellomrom på rare steder. Det dukker opp på eksamen også, og det er alltid oppgaveteksten som forteller deg det: «se bort fra linjer som begynner med #».

Hopp over tomme linjer

En tom linje i fila gir '' etter .strip(), og ''.split(';') gir [''] — ei liste med ett tomt felt. Prøver du å hente felt[2], får du IndexError.

Testen står som første linje i løkka:

for linje in f:
    linje = linje.strip()
    if linje == '':
        continue

Blokken over er starten på en løkke, ikke et program som kan kjøres alene.

continue hopper til neste runde i løkka uten å kjøre resten. Alternativet if len(linje) > 0: med resten inne i if-en gjør det samme; continue gir én innrykksnivå mindre og er lettere å lese.

Merk at fila i dette eksempelet renses én gang, øverst i løkka, og at split derfor kalles på den rensede linja etterpå.

Hopp over kommentarlinjer

En kommentarlinje er en linje som begynner med et bestemt tegn, oftest #. Testen bruker .startswith(...):

if linje.startswith('#'):
    continue

Utdraget hører hjemme inne i innlesingsløkka — continue er bare lovlig der.

s.startswith(tekst) gir True hvis strengen begynner med teksten. Den er tryggere enn linje[0] == '#', som krasjer på en tom linje — derfor kommer tomtesten alltid først.

Oppgaveteksten sier alltid hvilket tegn som gjelder. Finn det opp aldri selv: filtrerer du bort linjer som skulle vært med, blir alle senere svar feil.

✏️Eksempel 6: vaktlista med hull i
«Filen stemplinger.txt inneholder navn, ukedag og antall timer skilt med semikolon. Fila har tomme linjer, og linjer som begynner med # er kommentarer som skal ignoreres. Skriv les_vakter(filnavn) som returnerer en 2D-liste med timene som flyttall.»

Slik ser fila ut:

Innholdet i stemplinger.txt:

# vaktliste uke 11 - eksportert fra timeregistreringen
Hassan;mandag;7.5

Vera;mandag;6.0
Hassan;tirsdag;8.0

# helg foert manuelt
Vera;loerdag;4.5

To tester øverst i løkka, i denne rekkefølgen — tom først, kommentar etterpå:

def les_vakter(filnavn):
    f = open(filnavn, 'r')
    tabell = []
    for linje in f:
        linje = linje.strip()
        if linje == '':
            continue
        if linje.startswith('#'):
            continue
        felt = linje.split(';')
        felt[2] = float(felt[2])
        tabell.append(felt)
    f.close()
    return tabell

for rad in les_vakter('stemplinger.txt'):
    print(rad)

Utskrift:

['Hassan', 'mandag', 7.5]
['Vera', 'mandag', 6.0]
['Hassan', 'tirsdag', 8.0]
['Vera', 'loerdag', 4.5]

Sju linjer i fila, fire rader ut. To tomme og to kommentarer er borte.

Hvorfor rekkefølgen på testene betyr noe: hadde du skrevet if linje[0] == '#' i stedet, ville den krasjet med IndexError: string index out of range på den første tomme linja — for en tom streng har ingen posisjon 0. Med .startswith('#') er det trygt uansett, men tomtesten først er en god vane.

Slik ville sensor sett på det: de to continue-testene er verdt poeng i seg selv når oppgaveteksten ber om dem. Hopper du over dem, krasjer programmet på første tomme linje — og da får du null på deloppgaven selv om resten er perfekt.

Antakelsen å skrive ned: her antas det at en kommentarlinje aldri inneholder data, og at ingen navn begynner med #. Én setning om det holder.

📝Oppgave 11
Eksamensnivå, sjanger E

Skriv timer_per_person(filnavn) som leser stemplinger.txt og returnerer {navn: sum timer}, med tomme linjer og kommentarlinjer hoppet over.

Gjenbruk les_vakter fra eksempel 6 i stedet for å lese fila på nytt.

📝Oppgave 12
Eksamensnivå, sjanger E

Du får denne oppgaveteksten:

«Filen ladestasjoner.txt har overskriftsrad og feltene navn, kommune, uttak og pris. Skriv én innlesingsfunksjon som gjør det mulig å svare på begge disse spørsmålene senere: (1) hvilken stasjon er dyrest, og (2) hvor mange uttak har hver kommune til sammen?»

a) Hvilken struktur bør innlesingsfunksjonen returnere, og hvorfor?

b) Skriv funksjonen.

c) Skriv de to analysefunksjonene, og pass på at ingen av dem leser fila.

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.