1.3 Løkker — for, while, range og nøstede løkker
For- og while-løkker, `range` med tre argumenter, nøstede løkker og løkketerminering — kjernen i kodesporing og i sjanger D.
- Sjanger A — kodesporing («hva skriver dette programmet ut?») forekommer i 100 % av settene, og løkkesporing er den klart vanligste innpakningen på midtveiseksamen (UiOs digitale flervalgseksamen i oktober, 25 % av karakteren).
- Sjanger D — matche-tabell, i varianten «terminerer denne løkken, eller går den for alltid?». Den er et jevnlig innslag og gir 2–3,5 poeng.
- To feller går igjen år etter år: av-én-feilen i range (feilkode #1, den hyppigste feilen i hele faget), og nøstede løkker der den indre grensen avhenger av den ytre (for j in range(i), feilkode #12). Begge er rene tellefeil, og begge er lærbare.
Prioritet: dette må sitte (høyeste av bokas tre nivåer — «må sitte», «må kunne», «bør kjenne til»). Alt du gjør senere i faget er en løkke: rekker, differenslikninger og ODE-løsere er alle den samme konstruksjonen med ulikt innhold.
- Kap. 1.1 — Variabler, datatyper, uttrykk og operatorpresedens: tilordning, +=, sammenligninger og logiske uttrykk.
- Kap. 1.2 — Lister, indeksering og slicing: lister, len, append, og range som oppskrift på en tallrekke.
Har du ikke lest dem, er range(a, b) det du trenger å ta med deg: tallrekka fra og med a til men ikke med b.
Løkke 1 — For-løkka over en sekvens (~8 min)
Værstasjonen har levert fire døgnmålinger, og du vil skrive ut hver av dem. Du kunne skrevet fire print-setninger. Men med 8 760 timesmålinger i året går ikke det, og derfor har alle programmeringsspråk en konstruksjon som sier «gjør dette én gang for hvert element».
I Python heter den for, og den leses nesten som norsk.
Kjører kodeblokken under én gang for hvert element i sekvensen. Variabelen e peker på elementet i hver runde — ikke på plassnummeret.
En «sekvens» er noe det går an å gå gjennom: en liste, en streng, en tallrekke fra range. Kolon på slutten av for-linja er obligatorisk.
for grad in [12, 15, 9]:
print(grad)Utskrift:
12
15
9Alt som skal gjentas, står rykket inn under for-linja. Innrykket er ikke pynt — det er Pythons måte å avgjøre hva som hører til løkka.
Bokas standard er fire mellomrom per nivå, og du skal aldri blande mellomrom og tabulator i samme fil (feilkode #24). Den første linja som ikke er rykket inn, er utenfor løkka og kjøres bare én gang, etter at løkka er ferdig. Nettopp det skillet er en yndet kodesporingsfelle: står print inne i løkka, skrives det én linje per runde; står den utenfor, skrives det én linje til slutt.
Samme fire linjer, to plasseringer av print. Hva skriver programmet ut?
Først med print inne i løkka:
for grad in [12, 15, 9]:
total = grad * 2
print(total)Utskrift:
24
30
18Så med print utenfor, altså uten innrykk:
for grad in [12, 15, 9]:
total = grad * 2
print(total)Utskrift:
18Den andre versjonen skriver bare én linje, og den viser den siste verdien total fikk. Løkka kjørte tre ganger, men print kjørte én gang.
Legg også merke til at total fortsatt finnes etter at løkka er ferdig. Variabler som lages inne i en løkke, forsvinner ikke når løkka slutter — de beholder sin siste verdi.
(Innstegsoppgave — minimal sporing. Sjanger A — kodesporing, altså «hva skriver programmet ut?».) Hva skriver programmet ut?
for tekst in ['sol', 'regn']:
print(tekst)
print('ferdig')Løkke 2 — range, tellevariabel og akkumulator (~12 min)
Ofte vil du ikke gå gjennom en liste, men gjennom tallene 0, 1, 2, … — for eksempel fordi du summerer et matematisk uttrykk. Da bruker du range, og da må du telle nøyaktig.
Løkka kjører én gang for hver verdi i tallrekka. stop er eksklusiv.
Antall runder, når steget er 1, er stop - start:
- range(5) gir 5 runder, med i lik 0, 1, 2, 3, 4.
- range(1, 5) gir 4 runder, med i lik 1, 2, 3, 4.
- range(0, 10, 3) gir 4 runder, med i lik 0, 3, 6, 9.
Regelen «range(N) gir N runder» er verdt å pugge, for den er kilden til feilkode #1.
Dette er den viktigste tellesetningen i faget, og den forvirrer fordi bokstaven N brukes om to nære, men ulike ting.
Deler du et intervall i N like steg, får du N + 1 punkter — endepunktene begge medregnet. Tre steg gir fire punkter, akkurat som tre skritt gir fire fotavtrykk hvis du teller det du startet på.
Konsekvensen i kode: skal du oppdatere noe N ganger, skriver du for n in range(N). Skal du fylle N + 1 punkter, skriver du for i in range(N+1). Begge er riktige — i ulike oppgaver. Du møter det igjen i differenslikningene i kap. 6.2 og i ODE-løserne i kap. 7.1.
range(10, 0, -2) teller nedover: 10, 8, 6, 4, 2. Startverdien er med, sluttverdien er fortsatt eksklusiv, så 0 blir ikke med.Er steget negativt og start mindre enn stop, blir rekka tom og løkka kjører ingen ganger. range(0, 5, -1) gir ingen runder — ingen feilmelding, bare stille ingenting.
range(0) er en tom tallrekke, og en for-løkke over den kjører null ganger. Kroppen blir aldri utført, og det gis ingen feilmelding.Det er en yndet felle i nøstede løkker: for j in range(i) med i lik 0 gir ingen runder i det hele tatt. Konsekvensen er at den første runden i den ytre løkka ofte ikke skriver noe.
Det kanoniske mønsteret for å bygge opp én verdi av mange. Tre faste deler, i denne rekkefølgen:
s = 0
for i in range(1, 5):
s += i
print(s)Utskrift:
101. Startverdi før løkka. s = 0 for en sum, p = 1 for et produkt. Står den inne i løkka, nullstilles den hver runde, og resultatet blir bare det siste leddet — en av de mest lærerike feilene å ha gjort én gang.
2. Oppdatering inne i løkka, med +=.
3. Bruk av resultatet etter løkka, utenfor innrykket.
Dette er samme mønster som bærer alle rekke-oppgavene i kap. 3.2, der det står som s = 0; for k in range(N+1): s += ledd(k); return s. Lær det her, og du kan det der.
Programmet skal legge sammen . Hva skriver det ut, og hvor mange runder kjører løkka?
s = 0
for i in range(1, 5):
s += i**2
print(i, s)
print('sum:', s)Utskrift:
1 1
2 5
3 14
4 30
sum: 30Sporingstavle
| Runde | i | i**2 | s etter oppdatering | Skriver |
|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 1 1 |
| 2 | 2 | 4 | 5 | 2 5 |
| 3 | 3 | 9 | 14 | 3 14 |
| 4 | 4 | 16 | 30 | 4 30 |
Løkka kjører
5 - 1 = 4 runder, og i blir aldri 5. Merk at matematikken over har summasjonsindeks og at koden bruker i — det er bevisst. Bruk samme bokstav i formelen og i koden; da er sporingen langt lettere å kontrollere, både for deg og for sensor.Feilkode #1 (bokas nummerering av vanlige feil, samlet i kap. 0.1) er den hyppigste feilen i hele faget:
- Skal summen gå til og med N, altså , må du skrive range(N+1). range(N) stopper på N-1 og mister siste ledd.
- Skal du oppdatere en verdi N ganger, er range(N) riktig, og range(N+1) gjør én oppdatering for mye.
Spør deg derfor alltid: teller jeg steg eller punkter? Det er samme spørsmål som skiller N og N+1 i definisjonen over, og den forveksles i alt fra rekker til ODE-løsere.
Praktisk kontroll på eksamen: sett N = 2 og tell rundene på papir. Får du to eller tre? Da vet du hvilken variant oppgaven trenger.
(Midtveisnivå, sjanger A.) Hva skriver programmet ut?
s = 0
for i in range(4):
s += i
print(i, s)(Midtveisnivå, sjanger A — negativt steg.) I koden under står end=' ' inne i print. Det bytter ut linjeskiftet print normalt avslutter med, slik at verdiene havner etter hverandre på samme linje med et mellomrom imellom; et tomt print() avslutter linja. Mer om end i kap. 1.4.
Hva skriver programmet ut?
for i in range(6, 1, -2):
print(i, end=' ')
print()
for j in range(0, 4, -1):
print('aldri')
print('slutt')Løkke 3 — Løkke inne i løkke (~10 min)
— naturlig pausepunkt —
En nøstet liste krever en nøstet løkke: én løkke over radene, og inne i den én løkke over elementene i raden. Sporingen blir mekanisk så snart du innser at den indre løkka kjøres helt ferdig for hver enkelt runde i den ytre.
En løkke inne i kroppen til en annen løkke, med ett innrykksnivå mer.
Antall runder i den indre kroppen er produktet av rundene, når grensene er uavhengige: en ytre løkke over 3 verdier med en indre over 4 gir 12 runder i den innerste kroppen. Rekkefølgen er at den indre går helt ferdig, så tar den ytre ett skritt, så starter den indre på nytt fra begynnelsen.
Den indre grensen kan avhenge av den ytre løkkevariabelen: for j in range(i). Da varierer antall indre runder fra runde til runde.
Med for i in range(1, 4) og for j in range(i) blir det 1 + 2 + 3 = 6 runder i den innerste kroppen. Og med for i in range(3) blir første ytre runde helt tom, fordi range(0) ikke gir noen verdier.
Dette er feilkode #12, og det er den nest hyppigste kodesporingsfellen etter av-én. Metoden er alltid den samme: skriv en tabell med én rad per innerste runde.
Dette mønsteret er en gjenganger. Hva skriver programmet ut?
for i in range(1, 4):
for j in range(i):
print(i*j, end=' ')
print()Utskrift:
0
0 2
0 3 6 Sporingstavle — én rad per innerste runde:
Ytre i | Indre j fra | Runder | i*j-verdiene | Linja blir |
|---|---|---|---|---|
| 1 | range(1) | 1 | 0 | 0 |
| 2 | range(2) | 2 | 0, 2 | 0 2 |
| 3 | range(3) | 3 | 0, 3, 6 | 0 3 6 |
Tre detaljer avgjør oppgaven:
1.
range(i) starter alltid på 0, så første tall på hver linje er i*0, altså 0.2. Antall tall per linje er
i, ikke i+1.3.
print() uten argumenter står i den ytre kroppen (ett innrykksnivå), og avslutter linja etter hver ytre runde. Hadde den stått i den indre, ville hvert tall fått sin egen linje.Til sammen 1 + 2 + 3 = 6 tall.
(Midtveisnivå, sjanger A — nøstet løkke.) Hva skriver programmet ut?
for i in range(3):
for j in range(i, 3):
print(i + j, end=' ')
print()Løkke 4 — While-løkka og spørsmålet om terminering (~12 min)
En for-løkke vet på forhånd hvor mange runder den skal kjøre. Noen ganger vet du det ikke: «fortsett å halvere til tallet er under 1». Da bruker du while, og da får du et nytt problem — løkka kan gå for alltid.
Kjører kroppen så lenge betingelsen er sann. Betingelsen sjekkes før hver runde, så er den falsk fra starten, kjøres kroppen null ganger.
Det avgjørende er at noe inne i kroppen må endre betingelsen. Glemmer du det, er du i en uendelig løkke:
n = 10
while n > 1:
n = n // 2
print(n, end=' ')Utskrift:
5 2 1 Her er det n = n // 2 som til slutt gjør betingelsen falsk.
Standardmåten å telle hvor mange runder noe tok. Samme tredeling som akkumulatoren, men med while:
n = 100
antall = 0
while n > 1:
n = n // 3
antall += 1
print(antall, n)Utskrift:
4 11. Teller og starttilstand før løkka (antall = 0).
2. Endring av tilstanden pluss antall += 1 inne i kroppen. Rekkefølgen mellom de to har ingenting å si for sluttsvaret her, men den har det for hva som skrives ut hvis du skriver ut underveis — og det er nettopp den detaljen kodesporing spør om.
3. Resultatet brukes etter løkka.
Sporingen av dette mønsteret er: 100 → 33 → 11 → 3 → 1, altså fire runder. Legg merke til at n etter løkka er 1, ikke 0 — løkka stanset i det øyeblikket betingelsen ble falsk.
En løkke terminerer hvis betingelsen blir falsk etter et endelig antall runder. Spørsmålet «terminerer denne løkken?» er en egen oppgavesjanger (D), og metoden er alltid den samme:
1. Skriv ned tilstanden før første runde.
2. Regn ut de første tre-fire rundene.
3. Spør: beveger tilstanden seg mot betingelsens grense, og kan den treffe eller passere den?
Faller n med 3 om gangen fra 10 mot betingelsen n != 1, får du 10, 7, 4, 1 — treff, altså terminering. Starter du på 9, får du 9, 6, 3, 0, -3, … og passerer 1 uten å treffe: uendelig løkke. Det er ikke retningen alene som avgjør, men om verdien treffer betingelsen.
En while-løkke der betingelsen aldri blir falsk. Programmet henger til du avbryter det.
De to vanligste årsakene: kroppen endrer aldri det betingelsen ser på, eller den endrer det i feil retning. En tredje, mer lumsk variant er en betingelse med != som testverdien hopper over — som i eksempelet i definisjonen over. Bruk <= eller >= i stedet for != når du kan; da kan verdien passere grensen og løkka stopper likevel.
Avbryter løkka umiddelbart, uten å fullføre resten av kroppen og uten flere runder. Programmet fortsetter på første linje etter løkka.
I en nøstet løkke bryter break bare ut av den innerste løkka den står i — den ytre fortsetter. Det er en fast felle i kodesporing.
Hopper til neste runde uten å gjøre resten av kroppen. Løkka fortsetter som normalt.
Merk forskjellen mot break: continue hopper over resten av denne runden, break avslutter hele løkka. I en while-løkke er continue farlig hvis oppdateringen av tellevariabelen står nedenfor — da hoppes den over, og du har laget en uendelig løkke.
Hva skriver programmet ut?
for i in range(6):
if i == 2:
continue
if i == 4:
break
print(i, end=' ')
print('| ferdig')Utskrift:
0 1 3 | ferdigSporingstavle
| Runde | i | Hva skjer | Utskrift så langt |
|---|---|---|---|
| 1 | 0 | ingen av testene slår til | 0 |
| 2 | 1 | ingen av testene slår til | 0 1 |
| 3 | 2 | continue — hopper over print | 0 1 |
| 4 | 3 | skriver | 0 1 3 |
| 5 | 4 | break — løkka avsluttes | 0 1 3 |
5 blir aldri behandlet, for break avsluttet hele løkka. Så kjøres linja etter løkka.Merk at
2 mangler i utskriften, men at løkka fortsatte etterpå. Det er forskjellen mellom continue og break i én utskrift.(Midtveisnivå, sjanger A — while-løkke med teller.) Hva skriver programmet ut?
n = 40
antall = 0
while n > 2:
n = n // 3
antall += 1
print(n, end=' ')
print('|', antall)(Midtveisnivå, sjanger D — matche-tabell der du avgjør om løkka stopper.) Avgjør for hver av de fire løkkene om den terminerer eller går uendelig. Alle starter med n som angitt.
a) n = 10, løkka while n != 1: n = n - 3
b) n = 9, løkka while n != 1: n = n - 3
c) n = 5, løkka while n > 0: n = n + 1
d) n = 64, løkka while n > 1: n = n // 2
Løkke 5 — Når du trenger både plass og verdi: enumerate og zip (~10 min)
To hjelpere som gjør løkkene korte og lesbare — og som begge har en presis oppførsel du må kunne, fordi de er faste kodesporingsfeller.
Løkker over plassnumrene i lista i stedet for over elementene: for i in range(len(x)) gir i lik 0, 1, …, len(x) - 1.
Du bruker den når du trenger indeksen til noe — for eksempel når du skal skrive til x[i], eller sammenligne x[i] med x[i+1]. Trenger du bare verdiene, er for e in x penere, og sensor foretrekker det.
Gir paret (indeks, verdi) i hver runde, i den rekkefølgen. Du pakker det ut i to variabler:
for i, grad in enumerate([12, 15, 9]):
print(i, grad)Utskrift:
0 12
1 15
2 9Indeksen starter på 0. Rekkefølgen (indeks, verdi) — ikke omvendt — er feilkode #17, og den er lett å bytte om under press.
Går gjennom to sekvenser samtidig, og gir paret (a[i], b[i]) i hver runde.
Det avgjørende: zip stopper ved den korteste sekvensen. Er a fem lang og b tre lang, får du tre runder, og de to siste elementene i a blir aldri sett. zip fyller ikke ut, gir ingen feilmelding, og advarer ikke. Dette er den andre halvparten av feilkode #17, og den koster poeng hvert år.
Tre stasjonsnavn og fem målinger — merk hvor mange runder zip gir. Hva skriver programmet ut?
navn = ['Blindern', 'Tryvann', 'Bygdoy']
grader = [12, 15, 9, 11, 14]
for i, n in enumerate(navn):
print(i, n)
for n, g in zip(navn, grader):
print(n, g)
print(len(navn), len(grader))Utskrift:
0 Blindern
1 Tryvann
2 Bygdoy
Blindern 12
Tryvann 15
Bygdoy 9
3 5enumerate gir tre runder med indeks og navn. zip gir også bare tre runder — ikke fem — fordi navn er den korteste. Målingene 11 og 14 blir aldri brukt, og programmet sier ikke et ord om det.
Det er nettopp derfor zip er en god eksamensfelle: koden ser riktig ut, den kjører uten feilmelding, og likevel behandler den bare en del av dataene. Skal du være sikker på at alt blir med, må du sjekke lengdene selv.
(Midtveisnivå, sjanger A — zip og enumerate.) Hva skriver programmet ut?
a = [1, 2, 3, 4]
b = [10, 20]
for x, y in zip(a, b):
print(x + y, end=' ')
print()
for i, v in enumerate(b):
print(i, v, end=' | ')Hva skriver programmet ut?
for i in range(1, 4):
for j in range(1, 4):
if i * j > 4:
break
print(i * j, end=' ')
print('|', end=' ')De to programmene under ser nesten like ut. Hva skriver hvert av dem ut, og hvilken linje utgjør forskjellen?
Program 1:
s = 0
for i in range(1, 4):
s += i
print(s, end=' ')Program 2:
for i in range(1, 4):
s = 0
s += i
print(s, end=' ')Tre navnekollisjoner du bør være bevisst på i dette kapitlet, fordi de er kilden til feil som ser ut som slurv men er misforståelser:
- i er både løkkevariabelen i koden og summasjonsindeksen i formelen over koden. Det er ikke et problem — det er en fordel, og bokas regel er at de skal ha samme bokstav. Skriver formelen , skal løkka hete i.
- N er antall steg i noen oppgaver og N+1 er antall punkter. Se definisjonen «Antall steg og antall punkter» over. Blandingen er feilkode #1.
- n brukes både som løkkevariabel (for n in range(N)) og som «antall» (range(n)). Boka bruker n som løkkevariabel i differenslikninger og ODE-er (kap. 6.2, kap. 7.1), og N med stor bokstav om antallet. Hold deg til det.
- #1 — av-én i range. range(N) gir N runder og siste verdi N-1. Skal siste ledd N være med, må det stå range(N+1). Den hyppigste feilen i faget.
- #12 — grenser i nøstede løkker. for j in range(i) gir ulikt antall runder for hver i, og null runder når i er 0.
- #17 — zip og enumerate. zip stopper ved den korteste sekvensen, uten advarsel. enumerate gir (indeks, verdi) i den rekkefølgen.
- Startverdien inne i løkka. s = 0 hører før løkka, ellers nullstilles summen hver runde.
- Uendelig løkke med !=. Bruk <=/>= når verdien kan hoppe over grensen.
- Innrykket. En linje som ikke er rykket inn, kjøres én gang etter løkka — ikke én gang per runde.
Begrepsbank
Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
Å gå gjennom elementene i en samling, ett om gangen. En for-løkke itererer over en liste, en streng eller en tallrekke.
Ordet brukes også om hver enkelt runde: «i den tredje iterasjonen er i lik 2». I sporingstavlene i denne boka er «iterasjon» og «runde» samme ting.
En streng er også en sekvens, så for tegn in 'sol' gir tre runder, med tegn lik 's', 'o' og 'l'.
Det er derfor tegn-for-tegn-algoritmer (tell vokaler, sjekk palindrom) blir korte i Python. Mer om strenger i kap. 1.4.
Etter at en for-løkke er ferdig, finnes løkkevariabelen fortsatt, med den siste verdien den fikk. Etter for i in range(4) er i lik 3.
Ett unntak er verdt å kjenne: kjørte løkka null ganger, ble variabelen aldri laget, og et forsøk på å bruke den stopper programmet med en NameError. Det er en av exception-typene i kap. 2.2.
Bruk for når du vet hvor mange runder du trenger, eller når du går gjennom en samling. Bruk while når antall runder avhenger av noe som skjer underveis.
I IN1900 er for det vanlige: rekker, differenslikninger og ODE-løsere har alle et kjent antall steg. while dukker opp i iterative metoder som stopper på en toleranse — som Newtons metode i kap. 6.1 — og i kodesporingsoppgaver om tellere.
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.