en no

Nocturne.no

Utviklerblogg(en)
Innføring i Python

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.

  1. Lag et program som skriver kvadratet (dvs. tallet ganget med seg selv) for tallene fra 1 til 10.

  2. Lag et program som skriver alle kvadrattallene som er under 1000.

  3. Lag et program som ber brukeren om å skrive inn et tall, og deretter skriver en (forhåndsdefinert) setning det antallet ganger til skjermen.

  4. (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.


Håkon Flatval
Created with Hugo, theme based on nostyleplease.