Nocturne.no
Løkker
I denne posten skal vi se på løkker. Løkker er på mange måter den største grunnen til at programmering er nyttig til mange forskjellige formål. Løkker gjør det mulig å kjøre den samme koden mange ganger, noe som gjør oss i stand til å prosessere store mengder data raskt.
while-løkker
Helt spesifikt skal vi gå igjennom while-løkker i denne posten. Det er også en annen form for
løkker, for-løkker, som vi kommer til å gå igjennom i en senere post.
Løkker er ikke veldig vanskelige å forstå, men det kan ta lang tid å bli vant til å tenke og jobbe med løkker. Derfor introduseres de her i en egen post, så vi har plass til å vise en del tilfeller hvor løkker hører hjemme.
En while-løkke er på formen
while <betingelse>:
<løkkeblokk>
Den starter med en linje som begynner med while fulgt av en betingelse, og med et kolon på slutten. Deretter følger
en innrykket blokk, akkurat som i hvis-setningen vi så på i posten om betingelser.
while-løkken blir kjørt som følger: Når Python kommer til linjen som begynner på while, sjekker den om
betingelsen er sann. Hvis den er sann, kjører Python gjennom koden i løkkeblokken. Etter at den har nådd slutten
på den innrykkede blokken, hopper den tilbake til while-linja og sjekker betingelsen på nytt.
Dersom betingelsen er sann, kjører den igjennom løkkeblokken på nytt, og fortsetter slik igjen og igjen helt
til betingelsen er usann når Python leser while-linja. Når dét skjer, fortsetter Python fra første
linje etter løkkeblokken.
Én kjøring av løkkeblokken kaller vi en iterasjon, som er begrepet vi kommer til å bruke framover.
Vi kan hoppe rett på et eksempel:
i = 0
while i < 5:
print('Nå er i = ' + str(i))
i += 1
Kjører vi denne koden, får vi følgende utputt:
Nå er i = 0
Nå er i = 1
Nå er i = 2
Nå er i = 3
Nå er i = 4
Så, hva skjedde her?
Først definerer vi en variabel i til å være 0. i er
en variabel vi kommer til å øke for hver gang vi går igjennom løkken,
og som vi bruker i betingelsen i while-linja.
Deretter kommer vi til while-linja, og Python sjekker
om i er mindre enn 5. Det er den såvisst, og vi går
inn i løkkeblokken. Her kjøres en print()-setning,
som gir opphav til den første linja vi ser i utputt, som
forteller oss at i er 0.
Til slutt i while-blokken er en linje vi ikke har sett
før: i += 1. Dette er bare en kortform for i = i + 1,
som altså gjør at verdien til variabelen i økes med 1.
Deretter fortsetter Python fra while-linja igjen,
men denne gangen har variabelen i verdien 1.
Slik fortsetter Python kjøringen av løkkeblokken. Som vi ser
i utputt, kjøres print()-linja hver gang
Python går igjennom løkkeblokken, og vi ser også
at i økes for hver iterasjon.
Den siste gangen løkkeblokken kjøres, er i lik 4.
Verdien skrives til skjerm og i økes til 5.
Når Python så går tilbake til while-linja, finner
den at betingelsen ikke lenger er oppfylt.
Det er fordiulikhetsoperatoren < evalueres til usant hvis
de to sidene har samme verdi. Hvis vi ville inkludert tilfellet
hvor de var like, kunne vi brukt <=.
Etter å ha sett at løkkebetingelsen ikke er oppfylt,
hopper Python til etter løkkeblokken, og programmet avsluttes.
Når man tenker på løkker i programmering, er det fort
gjort å gjøre en feilberegning rundt hvilke verdier som
brukes i siste iterasjon av en løkke, på engelsk har
det fått det egne begrepet off-by-one-error. I dette
programmet kan det f.eks. være lett å forvente
at siste iterasjon vil bruke i med verdien 5,
men, som vi allerede har diskutert, er det absolutt ikke tilfellet.
En grei måte å unngå slike feil på, er å tenke nøye gjennom hva som skjer i løkken i første og i siste iterasjon, og passe på at det stemmer med hensikten du har med koden.
Legg merke til at vi bare trenger å endre ett tall i programmet for å bestemme hvor mange ganger programmet trenger å kjøre gjennom løkkeblokken. Dette er et gjennomgående mønster i programmering - hvis du først klarer å gjøre noe én gang, er det veldig lett å gjøre det mange ganger. Anstrengelsen for å gjøre ting mange ganger ligger ikke på programmererens skuldre, men blir tatt hånd om av datamaskinen. Prøv f.eks. å endre 5-tallet i programmet over til tallet 100 og la Python gjøre resten av jobben!
Når du skriver egne while -løkker, er det
fort gjort å glemme å øke variabelen du bruker i
løkkebetingelsen (ofte kalt iterasjonsvariabelen).
I slike tilfeller kan du ende opp
med at Python kjører løkken
i det uendelige, siden betingelsen aldri endrer seg og
forblir sann.
Hvis du ser at programmet ditt bruker uforholdsmessig lang
tid eller spyr ut ekstreme mengder utputt,
er det sannsynligvis dette som har skjedd. Ikke få panikk,
det er ikke farlig!
Får å avslutte en slik uendelig løkke, kan du avbryte
programmet. I IDLE gjøres dette med Shell -> Restart Shell,
eller Ctrl+F6 (Cmd+F6 på Mac) på tastaturet.
Om du kjører programmet i en terminal, kan du ofte bruke Ctrl+C.
Vi legger en blokk inne i en blokk
I det følgende eksempelet kommer vi til å bruke %-operatoren, også kalt
modulooperatoren, eller restoperatoren.
Den brukes med heltall, og gir resten etter divisjon av venstresiden på høyresiden.
Det betyr f. eks. at 5 % 3 gir 2 som svar, og 7 % 2 gir 1.
Om du ikke føler deg hundre prosent sikker på hvordan restregning fungerer, så er ikke det veldig viktig nå - det du trenger å vite, er at hvis a % b gir svaret 0, så er a delelig på b, og spesielt betyr dette at hvis a er et partall, vil a % 2 gi 0 som resultat.
Se på dette programmet:
i = 0
while i <= 10:
if i % 2 == 0:
print(str(i) + ' er et partall')
i += 1
Her har vi slengt en hvis-setning inn i while-løkken vår, som gjør at koden vår
skriver alle partall fra og med null, til og med ti til skjermen:
0 er et partall
2 er et partall
4 er et partall
6 er et partall
8 er et partall
10 er et partall
Det er egentlig ikke noe mystisk med denne koden, vi må bare puste rolig og holde tunga rett i munnen for å passe på at vi henger med på det som skjer:
Når Python kommer inn i løkka for første gang, er i lik
0. 0 er et partall, så i % 2 == 0 evalueres til sant, slik
at hvis-blokken kjøres og vi får en linje ut på skjermen.
Deretter tar hvis-blokken slutt, fordi neste linje har mindre
innrykk enn print()-linja. Dermed kjøres i += 1 i
hver iterasjon av løkka, uavhengig av om i er et partall eller
ikke: I neste iterasjon er i lik 1, som ikke er et partall, slik at
hvis-blokken ikke kjøres, men verdien til i økes til 2 likevel.
Legg merke til at vi har brukt <= i while-sjekken
for å sørge for at tallet 10 blir tatt med.
Som vi kan se over, er det ikke noe problem å kombinere while-løkker
med en hvis-setning. Det er fritt for å legge flere hvis-setninger
inn i løkkeblokken. Vi kan for eksempel skrive om programmet over
til å gi særskilt behandling til tallet 2:
i = 0
while i <= 10:
er_partall = i % 2 == 0;
if er_partall:
if i == 2:
print('2 er et partall, men det vet vi jo allerede')
else:
print(str(i) + ' er et partall')
i += 1
Her har vi lagt til en ny hvis-setning inne i den foregående
hvis-setningen. Denne hvis-setningen blir bare sjekket av
Python hvis betingelsen i den første hvis-setningen er
sann. Det eneste den gjør, er å sjekke om i har
verdien 2. Hvis ja, skriver den en tilpasset melding.
Hvis ikke, skriver Python det samme som før. Utputt blir dermed:
0 er et partall
2 er et partall, men det vet vi jo allerede
4 er et partall
6 er et partall
8 er et partall
10 er et partall
En oppmerksom leser så kanskje også at vi laget en ny variabel
i denne koden over: er_partall, som vi har satt til
å være lik betingelsen i % 2 == 0. Vi har deretter
brukt variabelen er_partall direkte i hvis-setningen.
Når vi lagrer en betingelse i en variabel, får variabelen
datatypen bool fra engelsk boolean. En boolsk
variabel er enten sann - i Python True - eller usann - False.
Stor forbokstav er viktig i både True og False.
Det gjør ingen forskjell i programmets oppførsel at vi
legger betingelsen inn i en variabel først, men det kan
gjøre koden lettere å lese. Med denne omskrivingen er det
klart for en leser at poenget med i % 2 == 0 i koden
er å sjekke om et tall er et partall. Vi kommer
ikke til å bry oss så mye om lesbarhet i denne bloggserien, men
det er noe som er veldig relevant om du vil gjøre det mulig
for andre å kunne samarbeide på koden din.
break og continue
Noen ganger vet vi ikke hvor lenge løkken vår kommer
til å måtte kjøre, og noen ganger vil vi hoppe over iterasjoner.
De to kommandoene break og continue kommer oss
til unnsetning - de er
laget for å gi oss mer kontroll over hvordan løkken kjøres.
break
Når du skriver break på en linje inne i en løkke,
avsluttes løkken, og Python fortsetter kjøringen av programmet
etter løkkeblokken. La oss si at vi vil finne det første tallet
større enn null som er delelig på både 24 og 17. Da kan vi skrive koden
slik:
i = 1
while True:
if i % 24 == 0 and i % 17 == 0:
break
i += 1
print('Tallet ' + str(i) + ' er delelig på 24 og 17')
Det første du kanskje legger merke til, er at vi har
skrevet True, altså “sant” i stedet for en betingelse
i while-setningen. Dette fungerer helt fint,
fordi True i seg selv er en betingelsesverdi (en
boolsk verdi), som alltid evalueres til sann. I praksis betyr
dette at hver gang Python leser while-setningen for
å finne ut om den skal kjøre løkkeblokken, finner den at svaret
er ja. Hvis vi ikke er forsiktige, kan dette føre til at
løkken aldri avsluttes - men det har vi naturligvis tenkt på:
Hvis-setningen inne i løkkeblokken evalueres til sann
dersom tallet vårt er delelig både på 24 og på 17 - som
er akkurat det vi ser etter. Så når
denne betingelsen er sann, kjører vi umiddelbart break for
å avslutte løkken. I dette tilfellet vet vi at i inneholder
tallet vi er ute etter, slik at vi kan skrive det til
skjerm etter løkken.
Utputt fra programmet over blir:
Tallet 408 er delelig på 24 og 17
Det er vel og bra - men nå lurer vi på; hva er det neste tallet
som oppfyller samme betingelse? Vi skriver om programmet
til å søke etter det neste tallet. Én måte å få til det
på, er å øke startverdien til 409, og begynne søket derfra,
men vi skal også gjøre det med continue.
continue
Når vi skriver continue inne i en løkke,
starter Python direkte på neste iterasjon, dvs. går tilbake
til while-linja og evaluerer betingelsen igjen, og
fortsetter derfra. Dette kan vi
bruke til å hoppe over iterasjoner vi ikke er interesserte
i. For å finne det andre tallet som er delelig på både
24 og 17, etter å ha funnet ut at det første er 408, er som følger:
i = 1
while True:
if i == 408:
i += 1
continue
if i % 24 == 0 and i % 17 == 0:
break
i += 1
print('Tallet ' + str(i) + ' er delelig på 24 og 17')
Det eneste vi har gjort her, er å slenge inn en ny hvis-setning
i starten av løkkeblokken. Formålet med denne hvis-setningen,
er å hoppe over iterasjonen dersom i er 408, slik
at ikke løkken stopper i hvis-setningen under.
Det er fristende å bare skrive continue alene inne
i hvis-blokken, men det vil føre til at neste iterasjon også
har i med verdi 408, og Python kommer til å
kjøre continue i all evighet til noen avslutter
programmet med makt. Når vi kjører continue, mister
vi nemlig resten av løkkeblokken, inkludert
linja i += 1, som sørger for at vi starter hver nye
iterasjon med en ny verdi i variabelen i. Vi må
altså sørge for at effekten av denne linja er med
når vi bruker continue, og legger denne linja
inn i hvis-blokken, før vi kjører continue.
continue kan være litt risikabelt å bruke
med while-løkker, som vi kan se
over. continue er lettere å bruke med for-løkker,
som vi skal se på om et par poster.
Løkker i løkker
Helt til slutt skal vi se på et enda mer komplisert eksempel hvor vi legger en løkke inn i en annen løkkeblokk. Hvis du allerede føler du har fått nok av løkker (og det er lov!), kan du hoppe over denne seksjonen og heller se på den senere, når du har fått mer trening i å resonnere med løkker.
Vi skal lage et program som skriver ut alle primtall under 1000. Et primtall er et tall som bare er delelig på seg selv og tallet 1. Den enkleste måten å finne alle primtall under 1000 på, er å gå gjennom hvert tall opp til 1000, og for hvert tall, sjekke alle tall mellom 1 og tallet selv, for å se om tallet er delelig på noen av dem.
Koden ser slik ut:
i = 2
while i < 1000:
er_primtall = True
j = 2
while j < i:
if i % j == 0:
er_primtall = False
break
j += 1
if er_primtall:
print(str(i) + ' er et primtall')
i += 1
Her er det mye å sette tennene i:
Først setter vi i lik 2. Dette er variabelen
vi kommer til å øke for hver iterasjon, og sjekke hvorvidt
den er et primtall. Vi starter på 2 fordi 1 ikke regnes
som et primtall.
Inne i løkken har vi laget en
variabel er_primtall, som vi har satt til å være sann.
Ideen her er at vi starter med å “gjette” at i er et
primtall, men hvis vi finner ut i den indre løkken at i ikke
kan være et primtall, setter vi denne variabelen til usann.
I tillegg lager vi variabelen j, som vi skal
bruke til å iterere gjennom den indre løkken. Vi skal
la j gå fra tallet 2 og opp til (men ikke med)
tallet i, og sjekke for hvert tall om det
deler i.
Denne delingssjekken foregår
i betingelsen til den indre hvis-setningen.
Dersom det viser seg at j deler i, kan vi med
sikkerhet si at i ikke kan være et primtall, og
vi setter er_primtall til usann og avslutter løkken.
Vær obs på at break inne i en dobbel løkke bare
går ut av den innerste løkken.
Når vi til slutt er ferdig i den indre løkken, sjekker vi
variabelen er_primtall. Hvis er_primtall er
sann, betyr det at vi ikke fant noe tall mellom 1
og i som deler i, slik at i må
være et primtall. Ellers må vi ha funnet et tall som
deler i, slik at i ikke kan være et primtall.
En enklere måte å forstå denne konstruksjonen på, er ved å se
på den store løkkeblokken (som tilhører den
første while-setningen) i isolasjon først, og
se om du skjønner hvordan den bestemmer om i er et
primtall.
Det var en munnfull! Her er det lett å gå i baret på flere måter,
for eksempel er det viktig at vi sjekker delelighet bare med
tall fra og med 2 og til, men ikke med, i, ettersom
primtall har lov til å være delelige på 1 og seg selv.
Hvis du forsto alt i denne seksjonen, har du en veldig
god forståelse av løkker, som er det desidert
vanskeligste av de grunnleggende programmeringskonseptene.
Oppsummering
Løkker er et ekstremt kraftig verktøy som kan ta tid å mestre. Ikke vær redd hvis du føler du henger etter, forståelsen av løkker er noe som modnes over tid. Jeg vil uansett anbefale å prøve deg på oppgavene på slutten av denne posten, for å bli komfortabel med å skrive løkker på egen hånd.
Dette kan sies å være blant de mest utfordrende postene i denne bloggen. I neste post roer vi ned temopet litt for å lære om funksjoner.
Oppgaver
Før du går i gang med oppgavene, vil jeg bare minne om den gule notisen litt lenger opp i posten. Den gir deg tips til hvordan du avslutter uendelige løkker, dersom uhellet først er ute - og det er fort gjort.
-
Lag et program som skriver kvadratet (dvs. tallet ganget med seg selv) for tallene fra 1 til 10.
-
Lag et program som skriver alle kvadrattallene som er under 1000.
-
Lag et program som ber brukeren om å skrive inn et tall, og deretter skriver en (forhåndsdefinert) setning det antallet ganger til skjermen.
-
(Vanskelig) Lag et gjetteprogram - sett en variabel til et tall mellom 0 og 100 som du velger selv. La brukeren prøve å gjette tallet, og fortell dem om tallet de gjettet er for høyt eller lavt. La brukeren gjette på nytt til de gjetter riktig, og avslutt programmet.