8.2 Filskriving, filtrering og fast-bredde-felt
Skrive en datastruktur tilbake til fil, filtrere uten å endre argumentet, og lese fast-bredde-felt med posisjonsskiving.
Fillesingsoppgaven — sjanger L, altså «les fila og bygg datastrukturen» — er tredelt i de fleste settene: (a) les fila til en nøstet ordbok, (b) skriv en datastruktur tilbake til fil, (c) filtrer og returner en ny ordbok. Del (a) var kap. 8.1; dette kapitlet er (b) og (c).
Hele oppgaven teller 8–12 poeng og forekommer i 8 av 8 gjennomgåtte avsluttende sett (100 %). Skriving og filtrering er den lettere halvparten: hver deloppgave er 2–4 poeng, og de vurderes uavhengig — bommer du på (a), kan du fortsatt få full uttelling på (b) og (c) ved å skrive dem som om (a) var riktig. Prioriteten er derfor må kunne.
Til slutt i kapitlet står fast-bredde-lesing, altså filer der feltene ikke skilles av et tegn men står i faste kolonner. Den er merket bør kjenne til: den har vært innom ett sett, teller lite, og sensor belønner riktig tenkning selv når kolonnetallene er feil. Les den sist, og ikke bruk mye tid på den før resten sitter.
Forkunnskaper — sist du var her
Fra kap. 8.1 tar vi med oss den nøstede ordboka og to måter å gå gjennom den på. Her er de oppfrisket med kjørt utskrift.
1. Strukturen vi skriver ut og filtrerer. Ytre nøkkel er stasjonsnavnet, indre nøkler er feltnavnene.
data = {'Kvitheia': {'temp': 7.9, 'nedbor': 1180},
'Nordvika': {'temp': 3.1, 'nedbor': 980}}
for navn, verdier in data.items():
print(f'{navn:10s}{verdier["nedbor"]:6d}')Utskrift:
Kvitheia 1180
Nordvika 980.items() gir nøkkel og verdi samtidig, og f-strengene f'{navn:10s}' og f'{...:6d}' gir fast feltbredde. Nøyaktig samme f-strenger brukes når linjene skal skrives til fil.
2. Innlesingsmønsteret vi bygger videre på. Denne funksjonen fra kap. 8.1 gjenbrukes flere steder i kapitlet, så du kan slå tilbake hit:
def les_stasjoner(filnavn):
data = {}
with open(filnavn) as infile:
for line in infile:
navn, temp, nedbor = line.strip().split(';')
data[navn] = {'temp': float(temp), 'nedbor': int(nedbor)}
return data
print(len(les_stasjoner('stasjoner.txt')))Utskrift:
4Datafila er den samme som i forrige kapittel, og du kan lage den selv på to sekunder — en tekstfil stasjoner.txt med disse fire linjene i samme mappe som programmet:
Kvitheia;7.9;1180
Sandvollen;5.4;760
Ospedalen;8.6;1420
Nordvika;3.1;980Trenger du å friske opp f-strenger, står de i kap. 1.4; ordbøker i kap. 1.5.
Løkke 1 — Skrive en datastruktur til fil (~15 min)
Et program som leser målinger, regner ut noe og bare skriver svaret i terminalen, har mistet resultatet i det du lukker vinduet. Derfor ber eksamensoppgaven deg lagre. Skrivingen er kortere enn lesingen: åpne fila i skrivemodus, og skriv én streng per linje.
Ett grep skiller seg fra alt annet du har gjort med print: du må sette linjeskiftet inn selv.
Åpner fila for skriving. Det andre argumentet er modusen: 'w' betyr write. Finnes ikke fila, opprettes den. Finnes den, blir innholdet slettet i samme øyeblikk fila åpnes — også hvis du ikke skriver noe etterpå. Det er ikke en advarsel om noe uvanlig; det er den normale oppførselen, og den er verdt å vite når du tester koden på ekte data.
Som ved lesing lukkes fila automatisk når with-blokken slutter. Navnet outfile er konvensjonen sensor forventer.
with open('demo3.txt', 'w') as outfile:
outfile.write('første kjøring\n')
with open('demo3.txt', 'w') as outfile:
outfile.write('andre kjøring\n')
with open('demo3.txt') as infile:
n = 0
for line in infile:
n += 1
print(line, end='')
print(n)Utskrift:
andre kjøring
1Den første linja er borte. Fila inneholder bare det siste with-blokken skrev.
Skriver strengen tekst til fila, nøyaktig slik den står — uten å legge til linjeskift. Vil du ha én linje per oppføring, må du selv avslutte med '\n'. Det er den ene forskjellen fra print, og den vanligste feilen i deloppgaven.
write godtar bare strenger. Har du et tall, må det konverteres med str(...) eller settes inn i en f-streng; ellers får du TypeError.
with open('demo2.txt', 'w') as outfile:
outfile.write(1180)Siste linje i feilmeldingen er:
Utskrift:
TypeError: write() argument must be str, not intEn ordbok knytter stasjonsnavn til årsnedbør. Skriv den til sum.txt med semikolon mellom feltene, én stasjon per linje, og vis at fila ble som tenkt.
Vi skriver med str(...) rundt tallet, og leser fila tilbake med lesemønsteret fra forrige kapittel. Å lese tilbake er den eneste måten å se at skrivingen ble riktig, og en god vane når du tester.
data = {'Kvitheia': 1180, 'Sandvollen': 760, 'Ospedalen': 1420}
with open('sum.txt', 'w') as outfile:
for navn in data:
outfile.write(navn + ';' + str(data[navn]) + '\n')
with open('sum.txt') as infile:
for line in infile:
print(line, end='')Utskrift:
Kvitheia;1180
Sandvollen;760
Ospedalen;1420print(line, end='') brukes fordi line alt har linjeskiftet i seg — hadde vi brukt vanlig print, ville det blitt en blank linje mellom hver stasjon. Merk at fila står i innsettingsrekkefølgen til ordboka.
(Midtveisnivå, sjanger A — kodesporing, altså «hva skriver dette programmet ut?».) Programmet skriver til en fil og leser den tilbake. Hva blir utskriften?
data = {'Nordvika': 980, 'Kvitheia': 1180}
with open('x1.txt', 'w') as outfile:
for navn in data:
outfile.write(navn + ' ' + str(data[navn]) + '\n')
with open('x1.txt') as infile:
for line in infile:
print(line, end='')Glemmer du linjeskiftet, skriver write oppføringene rett etter hverandre. Fila blir syntaktisk «gyldig», men datastrukturen er ødelagt, og feilen viser seg først når noen skal lese fila igjen:
data = {'Kvitheia': 1180, 'Sandvollen': 760}
with open('uten_linjeskift.txt', 'w') as outfile:
for navn in data:
outfile.write(navn + ';' + str(data[navn]))
with open('uten_linjeskift.txt') as infile:
linjer = 0
for line in infile:
linjer += 1
print('[' + line + ']')
print('Antall linjer:', linjer)Utskrift:
[Kvitheia;1180Sandvollen;760]
Antall linjer: 1Legg merke til 1180Sandvollen: tallet og neste navn har grodd sammen. Leser du fila tilbake med .split(';'), får du feltet '1180Sandvollen', og int() på det gir ValueError.
Vanen som fjerner feilen: skriv '\n' som siste ledd i hver write, og gjør det samtidig som du skriver selve linja — aldri som noe du skal huske etterpå.
Den andre halvdelen av samme felle er å skrive tall direkte: outfile.write(data[navn]) gir TypeError: write() argument must be str, not int. Bruk str(...) eller en f-streng.
Den idiomatiske skrivemåten når linja har flere felt: bygg hele linja som en f-streng, med separator og linjeskift inne i strengen, og send den til write i én bevegelse. Formatspesifikatorene er de samme som i print: f'{x:.1f}' gir én desimal, f'{n:d}' gir et heltall, f'{navn:10s}' gir fast tekstbredde.
Fordelen er at antall desimaler i fila blir kontrollert. Uten formatering skriver Python flyttall med alle sifrene sine, og filer med 7.900000000000001 er vondt å lese.
d = {'Vest': {'ph': 7.4}, 'Aust': {'ph': 8.1}}
for stasjon, verdier in d.items():
print(f'{stasjon};{verdier["ph"]:.2f}')Utskrift:
Vest;7.40
Aust;8.10Her går linjene til skjermen for å vises, men strengene er nøyaktig de samme du ville sendt til write — med '\n' lagt til på slutten.
Skriv en nøstet ordbok til rapport.txt slik at fila kan leses tilbake av innlesingsmønsteret fra forrige kapittel: én header-linje med kolonnenavn, deretter én linje per stasjon med semikolon mellom feltene.
Headeren skrives én gang, før løkka. Deretter én f-streng per stasjon.
data = {'Kvitheia': {'temp': 7.9, 'nedbor': 1180},
'Nordvika': {'temp': 3.1, 'nedbor': 980}}
with open('rapport.txt', 'w') as outfile:
outfile.write('stasjon;temp;nedbor\n')
for navn, verdier in data.items():
outfile.write(f'{navn};{verdier["temp"]:.1f};{verdier["nedbor"]:d}\n')
with open('rapport.txt') as infile:
for line in infile:
print('[' + line.strip() + ']')Utskrift:
[stasjon;temp;nedbor]
[Kvitheia;7.9;1180]
[Nordvika;3.1;980]Fila er nå symmetrisk med innlesingen: skriver du en header, må leseren hoppe over den med readline(). Skriver du ingen, må leseren ikke gjøre det. Nevn valget i en kommentar hvis oppgaveteksten ikke sier hva som forventes — det er en rimelig antakelse som skal dokumenteres, og sensor godtar begge så lenge du er konsekvent.
Gitt en nøstet ordbok med stasjonsnavn som nøkkel og {'temp': ..., 'nedbor': ...} som verdi.
a) Skriv skriv_til_fil(data, filnavn) som lagrer strukturen med en header-linje stasjon;temp;nedbor og deretter én semikolonseparert linje per stasjon.
b) Kall funksjonen på en ordbok med Kvitheia (7.9, 1180) og Ospedalen (8.6, 1420), og les fila tilbake for å vise resultatet.
Løkke 2 — Filtrering uten å ødelegge originalen (~20 min)
— naturlig pausepunkt —
Del (c) lyder nesten alltid slik: «skriv en funksjon som returnerer en ny ordbok med bare de oppføringene som oppfyller et krav, uten å endre argumentet». De to uthevede ordene er hele oppgaven, og de er der fordi det er det man gjør galt.
Grunnen til at kravet stilles, er praktisk: den som kaller funksjonen din, skal fortsatt ha hele datasettet etterpå. En filterfunksjon som spiser originalen, er ubrukelig i et program som skal filtrere på flere grenser.
Fire steg, og de er alltid de samme:
1. Lag en tom ordbok: ny = {}.
2. Gå gjennom originalen med for nøkkel, verdi in data.items():.
3. Test betingelsen med if, og sett bare de kvalifiserte inn i ny.
4. return ny — utenfor løkka.
Originalen leses, aldri skrives. I kode:
def storre_enn(d, grense):
ny = {}
for k, v in d.items():
if v > grense:
ny[k] = v
return ny
d = {'Kvitheia': 1180, 'Sandvollen': 760}
print(storre_enn(d, 1000))
print(d)Utskrift:
{'Kvitheia': 1180}
{'Kvitheia': 1180, 'Sandvollen': 760}Nederste linje er beviset: originalen er urørt. Nettopp den utskriften er det oppgaven ber deg vise, og sensor ser etter tre ting — at ny opprettes tom, at betingelsen står i en if inne i løkka, og at return står utenfor.
Filtreringens to faste linjer. Den tomme ordboka ny er der resultatet samles; .items() gir nøkkel og verdi samtidig, slik at betingelsen kan stilles på verdien og nøkkelen tas med videre.
Er verdien selv en ordbok (nøstet struktur), stiller du betingelsen på et av de indre feltene: if verdier['nedbor'] > grense:. Skriv da inn en ny indre ordbok med de feltene du vil beholde — det gjør resultatet helt uavhengig av originalen.
data = {'Kvitheia': {'temp': 7.9}, 'Nordvika': {'temp': 3.1}}
ny = {}
for navn, verdier in data.items():
if verdier['temp'] > 5.0:
ny[navn] = {'temp': verdier['temp']}
print(ny, len(data))Utskrift:
{'Kvitheia': {'temp': 7.9}} 2Skriv mye_nedbor(data, grense) som returnerer en ny nøstet ordbok med bare stasjonene som har mer nedbør enn grense. Argumentet skal ikke endres. Vis at originalen fortsatt har alle fire stasjonene.
Den indre ordboka bygges på nytt, felt for felt, slik at hverken den ytre eller de indre ordbøkene deles med originalen.
def mye_nedbor(data, grense):
ny = {}
for navn, verdier in data.items():
if verdier['nedbor'] > grense:
ny[navn] = {'temp': verdier['temp'], 'nedbor': verdier['nedbor']}
return ny
data = {'Kvitheia': {'temp': 7.9, 'nedbor': 1180},
'Sandvollen': {'temp': 5.4, 'nedbor': 760},
'Ospedalen': {'temp': 8.6, 'nedbor': 1420},
'Nordvika': {'temp': 3.1, 'nedbor': 980}}
vaate = mye_nedbor(data, 1000)
print(vaate)
print(len(data), len(vaate))Utskrift:
{'Kvitheia': {'temp': 7.9, 'nedbor': 1180}, 'Ospedalen': {'temp': 8.6, 'nedbor': 1420}}
4 24 2 er det tallparet oppgaven vil se: fire i originalen, to i resultatet. Merk at grensen er streng (>), så en stasjon med nøyaktig 1000 mm ville falt utenfor. Sier oppgaveteksten «minst», skal det være >= — les kravet ordrett, og skriv en kommentar hvis det er tvetydig.
Bruk samme struktur som i Eksempel 3.
a) Skriv kalde(data, grense) som returnerer en ny nøstet ordbok med bare stasjonene der 'temp' er lavere enn grense. Argumentet skal være urørt etterpå.
b) Kall funksjonen med grensen 6.0 på Kvitheia (7.9, 1180), Sandvollen (5.4, 760) og Nordvika (3.1, 980), og skriv ut både resultatet og antall oppføringer i originalen.
ny = data lager ikke en kopi. Den gir bare et nytt navn på samme ordbok, så alt du gjør med ny, skjer med data:def mye_nedbor_feil(data, grense):
ny = data
for navn in ny:
if ny[navn]['nedbor'] <= grense:
ny[navn]['nedbor'] = 0
return ny
data = {'Kvitheia': {'nedbor': 1180}, 'Sandvollen': {'nedbor': 760}}
resultat = mye_nedbor_feil(data, 1000)
print(resultat)
print(data)Utskrift:
{'Kvitheia': {'nedbor': 1180}, 'Sandvollen': {'nedbor': 0}}
{'Kvitheia': {'nedbor': 1180}, 'Sandvollen': {'nedbor': 0}}De to linjene er identiske: originalen er ødelagt, og Sandvollens 760 mm er borte for godt. Dette er feil #23, aliasing — to navn på samme objekt. Funksjonen returnerer heller ikke det oppgaven ba om; den returnerer alle stasjonene, med nuller i stedet for de bortfiltrerte.
Regelen er enkel og uten unntak i denne sjangeren: skriv aldri ny = data. Skriv ny = {}, og bygg opp.
En finere variant av samme felle. Setter du inn hele verdien slik den står — ny[navn] = verdier — får du en ny ytre ordbok, men de indre ordbøkene er de samme objektene som i originalen. Endrer noen et indre felt i resultatet, endres originalen også.
For eksamen er dette godt nok: oppgaven ber om en ny ordbok, og en ny ytre ordbok er en ny ordbok. Men skal resultatet være helt uavhengig, bygger du den indre på nytt: ny[navn] = {'temp': verdier['temp'], 'nedbor': verdier['nedbor']}.
data = {'Kvitheia': {'nedbor': 1180}, 'Nordvika': {'nedbor': 980}}
ny = {}
for navn, verdier in data.items():
if verdier['nedbor'] > 900:
ny[navn] = verdier
ny['Kvitheia']['nedbor'] = 0
print(ny)
print(data)Utskrift:
{'Kvitheia': {'nedbor': 0}, 'Nordvika': {'nedbor': 980}}
{'Kvitheia': {'nedbor': 0}, 'Nordvika': {'nedbor': 980}}Nullen slo gjennom til originalen, selv om den ytre ordboka var ny.
(Midtveisnivå, sjanger A — kodesporing.) Hva skriver dette programmet ut?
def rens(data):
for navn in data:
data[navn] = data[navn] * 2
return data
d = {'a': 3, 'b': 5}
e = rens(d)
print(e)
print(d)
print(len(d), len(e))Fila stasjoner.txt er den fra Forkunnskaper: navn;temp;nedbor, uten header.
a) Bruk les_stasjoner fra kap. 8.1 og skriv kalde_stasjoner(data, grense) som returnerer en ny ordbok med stasjonene under en temperaturgrense, uten å endre argumentet.
b) Skriv skriv(data, filnavn) som lagrer en slik ordbok til fil med linjer navn;temp og temperaturen med én desimal.
c) Kjør hele kjeden med grensen 5.0: skriv ut den filtrerte ordboka, antall oppføringer i original og resultat, og innholdet i fila.
Løkke 3 — Fast-bredde-felt (bør kjenne til) (~10 min)
Dette delkapitlet er merket «bør kjenne til». Det har vært innom ett av de gjennomgåtte settene og teller få poeng. Les det når resten av kapitlet sitter, ikke før.
Noen datafiler har ingen separator. Feltene står i stedet i faste kolonner, slik målestasjoner og observatorier har lagret data i femti år: tegn 1–11 er navnet, tegn 12–19 er datoen, og så videre. Da kan ikke .split() brukes til noe — det finnes ikke noe skilletegn å dele på. I stedet klipper du ut tegnposisjoner med slicing.
Fila under bruker faste kolonner. Du kan lage den selv, men vær nøye: mellomrommene er data her, så antall mellomrom må stemme.
Innholdet i dybder.txt:
Kvitheia 20240301 4.2 118
Sandvollen 20240301 -1.8 43
Ospedalen 20240302 6.5 205Hver linje er nøyaktig 30 tegn: navn i posisjon 0–10, dato i 11–18, temperatur i 19–24 og snødybde i 25–29.
Slicing på linja klipper ut tegnene fra posisjon a til og med b-1 — slutten er eksklusiv, som alltid ved slicing. Resultatet er en streng som typisk har mellomrom rundt seg, så .strip() må på hvert utsnitt før du bruker det. Tallfelt må i tillegg konverteres.
Vær oppmerksom på at Python teller fra 0, mens kolonnebeskrivelser i dokumentasjon ofte teller fra 1. «Feltet står i kolonne 1–11» blir line[0:11] i Python.
line = 'Kvitheia 20240301 4.2 118\n'
print('[' + line[0:11] + ']')
print('[' + line[11:19] + ']')
print('[' + line[19:25] + ']')
print('[' + line[25:30] + ']')Utskrift:
[Kvitheia ]
[20240301]
[ 4.2]
[ 118]Merk at float(' 4.2') og int(' 118') fungerer selv uten .strip() — tallkonvertering tåler blanktegn. Det er tekstfeltene som må strippes.
Les dybder.txt til en ordbok med stasjonsnavn som ytre nøkkel, dato som neste nøkkel, og {'temp': ..., 'dybde': ...} innerst.
Fire slicing-uttrykk erstatter .split(). Resten av mønsteret — den ytre sjekken, typekonverteringen — er identisk med det du kjenner.
data = {}
with open('dybder.txt') as infile:
for line in infile:
navn = line[0:11].strip()
dato = line[11:19]
temp = float(line[19:25])
dybde = int(line[25:30])
if navn not in data:
data[navn] = {}
data[navn][dato] = {'temp': temp, 'dybde': dybde}
print(data['Sandvollen'])
print(data['Ospedalen']['20240302']['dybde'])Utskrift:
{'20240301': {'temp': -1.8, 'dybde': 43}}
205Bare navnet trenger .strip(); datoen fyller hele feltet sitt, og tallene konverteres uansett. Å skrive .strip() på alle fire er likevel trygt og koster ingenting.
Å treffe kolonnegrensene helt riktig på en hjelpemiddelfri eksamen er nesten umulig — du sitter uten fila, med bare oppgavetekstens gjengivelse, og skal telle mellomrom i hodet. Det vet sensor. Uttellingen ligger i å vise at du forstår metoden: slicing på faste posisjoner, .strip() på tekstfeltene, typekonvertering på tallfeltene, og resten av mønsteret som før. Bommer du med ett tegn på grensene, mister du kanskje et halvt poeng.
Så: skriv koden med de tallene du kommer fram til, og legg til en kommentar om hva du antar. En rimelig antakelse dokumentert i en kommentar er nettopp det sensor ber om når oppgaveteksten er underspesifisert:
line = 'Sandvollen 20240301 -1.8 43\n'
# antar navn i posisjon 0-10, dato 11-18, temp 19-24, dybde 25-29
print(int(line[25:30]), float(line[19:25]))Utskrift:
43 -1.8Den andre fellen er å glemme .strip() på tekstfeltene. line[0:11] er 'Sandvollen ' med et mellomrom på slutten, og brukt som ordboknøkkel gir det en nøkkel du aldri finner igjen når du senere slår opp 'Sandvollen'.
(Bør kjenne til, sjanger L — fast-bredde-felt.) Bruk dybder.txt fra dette delkapitlet, med snødybden i posisjon 25–29.
a) Skriv koden som summerer snødybden over alle tre linjene og skriver ut summen, antall linjer og gjennomsnittet med én desimal.
b) Forklar i én setning hvorfor .split() ikke kan brukes på denne fila.
Programmet skal skrive en ordbok til fil, én stasjon per linje, og lese den tilbake. Utskriften blir feil.
data = {'Kvitheia': 1180, 'Ospedalen': 1420}
with open('x3.txt', 'w') as outfile:
for navn in data:
outfile.write(navn + ';' + str(data[navn]))
with open('x3.txt') as infile:
for line in infile:
print(line.strip().split(';'))a) Hva skriver programmet ut, og hva var meningen?
b) Rett feilen, og vis den nye utskriften.
Begrepsbank
Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
Det andre argumentet til open. 'w' åpner fila for skriving og tømmer den umiddelbart; uten modusargument åpnes fila for lesing. Det finnes flere moduser i Python, men IN1900 trenger bare disse to — les og skriv. Åpner du en fil i 'w' og krasjer før du skriver noe, står du igjen med en tom fil.
print legger til linjeskift og gjør tall om til tekst for deg. write gjør ingen av delene: du må skrive '\n' selv, og du må konvertere tall med str(...) eller en f-streng. Til gjengjeld skriver write nøyaktig det du ber om, noe som er poenget når fila skal leses av et program senere.En fil du skriver, skal kunne leses tilbake av ditt eget innlesingsmønster. Skriver du en header-linje, må leseren hoppe over den med readline(); skriver du ingen, må den ikke. Velger du semikolon som separator, må leseren bruke .split(';'). Er oppgaveteksten uklar om header, velg selv og dokumenter valget i en kommentar.
Å returnere en ny ordbok med de oppføringene som oppfyller et krav, og la argumentet være urørt. Mønsteret er ny = {} → løkke over .items() → if-betingelse → innsetting → return ny utenfor løkka. Dette er del (c) av eksamensoppgaven, og formuleringen «uten å endre argumentet» står i oppgaveteksten fordi det er det som testes.
Å endre ordboka som ble sendt inn i funksjonen, i stedet for å bygge en ny. Fordi en ordbok sendes som en referanse til ett objekt, ser den som kalte funksjonen endringen umiddelbart. Det er feil #20, og det gjør funksjonen ubrukelig for den som skal filtrere samme datasett på flere grenser.
To navn på samme objekt. ny = data kopierer ikke ordboka — den gir bare et nytt navn, så endringer via ny skjer i data. Samme sak gjelder ett nivå ned: ny[k] = verdier gir en ny ytre ordbok, men de indre ordbøkene er felles. Feil #23. En helt uavhengig kopi krever at du bygger de indre ordbøkene på nytt, felt for felt.
En tekstfil der feltene ikke skilles av et tegn, men står i faste tegnposisjoner, med mellomrom som utfylling. .split() er ubrukelig; feltene hentes med slicing line[a:b] og strippes. Formatet er vanlig i eldre måleserier. I IN1900 er dette bør kjenne til — nivå 3, få poeng.
Sensors uttalte praksis: å telle tegnposisjoner helt riktig uten fila foran seg er nesten umulig, så uttellingen ligger i metoden — slicing på faste posisjoner, .strip() på tekstfelt, konvertering av tallfelt, resten av mønsteret som før. Skriv de tallene du kommer fram til, dokumenter antakelsen i en kommentar, og gå videre.
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 Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.