Uke 4 — Løkker / while / for

Vi skal se på noen eksempler på while-loops og for-loops, hvor vi kan repetere instruksjoner flere ganger.

I https://automatetheboringstuff.com/2e/chapter2/ finnes en god oversikt over alle tre muligheter for kontroll av programflyt vi skal bruke: if, while, og for.

Denne uken vær sikker på at du har god kontroll over hvordan innrykk fungerer i python. De er viktige for å definere rekkefølgen og strukturen til koden din.

Obs

Når vi bruker løkker er det mulig å lage program som aldri stopper.

Om du tror at koden din er i en uendelig loop kan du avslutte kjøringen ved å taste ctrl-c i terminalen som kjører koden.

Eksempler

Eksempel 1

While-løkker (while-loops) brukes i situasjoner når løkka skal utføres så lenge noe er sant.

Her er noen eksempler på while-løkker.

Last ned filen her: eksempel_while.py. Prøv først å si hva som kommer til å skje når du kjører koden. Et tips er å selv gå igjennom de første iterasjonene av hver loop ved hjelp av penn og papir og se hva som skjer. Så kjøre koden og se om det var riktig.

print("Countdown...")
num = 10  # start
while num > 0:  # test
    print(num)
    num -= 1  # update


# first square less than 1000
n = 1  # start
while n * n < 1000:  # test
    n += 1  # update
print(n, "is the first number where n^2 is larger than 1000")

# sleepy triangle
s = ""  # start
while s != "ZZZZZZZZZZZ":  # test
    print(s)
    s += "Z"  # update

I koden ser du at det er merket med ’start’, ’test’ og ’update’. En while-loop er avhengig av et vilkår og kjøres om vilkåret er oppfylt. Vilkåret er linja som er merka med ’test’.

Vilkåret i while-løkker trenger ofte en startverdi som oppdateres mens løkken utføres. Dette er linja merket med ’start’ (startverdi) og ’update’ (oppdateringen) i koden. Litt senere skal vi se hva som skjer om man for eksempel ikke oppdaterer startverdien i løpet av løkken.

Oppgave 1

I uke_04_oppg_1.py skriv en while-løkke som skriver ut alle tall som delbare med tre fra \(1\) til \(100\).

Husk at du trenger tre ting for en while-løkke: (1) start, (2) test, og (3) update.

Eksempelkjøring:

3
6
9
12
...

Oppgave 2

I uke_04_oppg_2.py skriv en while-løkke som beregner fakulteten (\(n!\)) til et heltall fra brukeren. For eksempel, 5! = 5 * 4 * 3 * 2 * 1 = 120. Fakulteten til 0 er definert som 1.

Eksempelkjøring:

Tall: 5̲
120

Eksempelkjøring:

Tall: 0̲
1

Oppgave 3

I uke_04_oppg_3.py bruk while-løkker for å skrive ut dette mønsteret (for hver rad øker/minsker vi med \(2\) mellomrom):

Eksempelkjøring:

--*
----*
------*
--------*
----------*
--------*
------*
----*
--*

Husk å oppdatere tellingen på slutten av løkken!

Obs

Du må ikke skrive dette som én løkke. Du kan skrive flere løkker etter hverandre.

Eksempel 2

Her er noen eksempler på break og continue. Man bruker break til å avbryte en løkke. Man bruker continue til å fortsette neste iterasjon av løkken før man kommer til slutten av en blokk.

Last ned filen her: eksempel_break_continue.py. Prøv først å si hva som kommer til å skje når du kjører koden. Kjør så koden og se om det var riktig.

# Use a counting loop to do something 7 times.
n = 0
while n < 7:
    print("Hello!")
    n += 1


# Read input 5 times:
n = 0
while n < 5:
    name = input(f"Name {n+1}? ")
    print("Hello,", name)
    n += 1


# Running an endless loop with a break condition is a common pattern.

while True:
    answer = input("Should we continue? ")
    if answer == "n":
        break
    print("Great, let's go one more time...")

print("Bye!")


##################################################
# "continue" can be used to jump over something. What does the code below jump over?

# If you are confused about what this code does you can include
# more print statements.
for i in range(100):
    if "4" in str(i):
        continue
    print(i)

Se på den siste løkken. Den kan skrives om ved hjelp av if-else sånn at man ikke må bruke continue. Finner du ut hvordan man kan gjøre det? Kan løkken med break skrives om sånn at vi får det samme resultatet uten å bruke break?

Både break og continue kan også brukes med for-loops som kommer nedenfor.

Oppgave 4

I uke_04_oppg_4.py lag en løkke for å snakke med en dum chatbot, bruk en while True løkke . Chatboten kan bare si tre ting: Hei! Vil du snakke med meg?, kult!, eller Ha det bra! Du må bruke input() for å svare chatboten. Bruk break for å avlutte while-løkken når du får inn Nei eller nei.

Eksempelkjøring (understrek viser svaret tastet inn av brukeren):

Hei! Vil du snakke med meg? J̲a̲
Så kult!
Hei! Vil du snakke med meg? J̲a̲
Så kult!
Hei! Vil du snakke med meg? N̲e̲i̲
Ha det bra!

Eksempel 3

Her er noen eksempler på for-løkker (for-loops). Last ned filen her: eksempel_for.py. Prøv først å si hva som kommer til å skje når du kjører koden. Kjør så koden og se om det var riktig. Merk spesielt hvilke tall du får fra løkkene med range(). Hva er det minste tallet du får? Hva er det største? Hva om du bruker tre tall som argument til range()? Prøv selv med noen ulike tall.

for letter in "Hello":
    print(letter)

print("=" * 25)

for mitt_langt_variabelnavn_som_jeg_kan_velge_fritt in "Hello":
    print(mitt_langt_variabelnavn_som_jeg_kan_velge_fritt)
    
print("=" * 25)

x = "Hello"
for letter in x:
    print(letter)
    
print("=" * 25)

print("Range 1:")
for n in range(7):
    print(n)

print("Range 2:")
for n in range(3, 7):
    print(n)

print("Range 3:")
for n in range(2, 17, 3):
    print(n)

#########################
# 2 ways to do the same
#########################

print("Afternoon 1:")
result = ""
for letter in "Good afternoon":
    if letter == "o":
        result += "0"
    else:
        result += letter
print(result)

print("Afternoon 2:")
result = ""
for letter in "Good afternoon":
    if letter == "o":
        result += "0"
        continue
    result += letter
print(result)

Sammenligne output fra de siste to løkkene. Hvilken løkke er lettere for deg å forstå?

Oppgave 5

I uke_04_oppg_5.py lag en for-løkke med range() som skriver ut tallene i \(6\)-gangen opp til og med \(36\).

Eksempelkjøring:

6
12
18
24
30
36

Oppgave 6

I uke_04_oppg_6.py skriv en for-løkke som teller ned fra \(10\) til \(1\) og så skriver «LIFTOFF!»

Eksempelkjøring:

10
9
⋮
2
1
LIFTOFF!

Oppgave 7

I uke_04_oppg_7.py skriv en for-løkke som skriver ut bokstavene i "Taumatawhakatangihangakoauauotamateaturipukakapikimaungahoronukupokaiwhenuakitanatahu" (dette navnet på en ås i New Zealand er verdens lengste stedsnavn) på hver sin linje.

Eksempelkjøring:

T
a
u
m
...

Eksempel 4

Alle for-løkker kan skrives om til while-løkker. Her er et eksempel.

Last ned og kjør filen her: eksempel_for_to_while.py. Skjønner du hva som skjer i begge løkkene?

# Every for-loop can be written as a while-loop:

print("For-loop")
for x in range(2, 19, 3):
    print(x)

print("While-loop")
x = 2
while x < 19:
    print(x)
    x += 3

Kan du skrive om en av for-løkkene med range() fra eksempel 3 til en while-løkke?

Det finnes ofte flere ulike måter til å gjøre det samme ting med programmering. Noen er enklere å bruke enn andre, og det kommer an på konteksten hva som er den beste muligheten.

Obs

Når vi vet på forhånd hvor mange iterasjoner vi trenger er det vanlig å bruke for. Der unngår vi at vi må håndtere startverdien, test og oppdatering av loop-verdien selv.

while-løkker bruker vi når vi ikke vet hvor mange iterasjoner vi trenger for å finne et resultat. Det kan være at stop-betingelsen er komplisert, eller at vi må ta input fra brukeren flere ganger frem til den passer.

Oppgave 8

I uke_04_oppg_8.py bruk for-løkker for å skrive ut det samme mønsteret som i oppgave 3.

Eksempel 5

Her er et eksempel på problemløsning ved hjelp av en for-loop i Python. Vi skal løse Problem 1 fra projecteuler.net:

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

Så hvordan går vi fra problem til kode? Denne prosessen kan deles opp i to deler.

  1. Finne ut en algoritme som løser problemet.

  2. Implementere algoritmen i valgt programmeringsspråk (i vårt tilfelle, Python).

Algoritme

La oss begynne med den første delen. Vi vil finne ut et sett med instrukser som gir summen vi trenger. Om vi ville beregnet summen opp til 10 selve, med papir og penn, ville vi sannsynligvis tenkt/gjort noe slikt:

  • 1 er ikke et multiplum av 3 eller 5, så gjør vi ingen med dette tallet.

  • 2 er ikke et multiplum av 3 eller 5, så gjør vi ingen med dette tallet.

  • 3 er et multiplum av 3, så vi må ta med tallet i summen. Summen vår blir da 3.

  • 4 er ikke et multiplum av 3 eller 5, så gjør vi ingen med dette tallet.

  • 5 er et multiplum av 5, så vi må ta med tallet i summen. Summen vår blir da 3 + 5 = 8.

  • 6 er et multiplum av 3, så vi må ta med tallet i summen. Summen vår blir da 8 + 6 = 14.

  • 7 er ikke et multiplum av 3 eller 5, så gjør vi ingen med dette tallet.

  • 8 er ikke et multiplum av 3 eller 5, så gjør vi ingen med dette tallet.

  • 9 er et multiplum av 3, så vi må ta med tallet i summen. Summen vår blir da 14 + 9 = 23.

Vi prøver å sammenfatte hva vi gjør her. Vi går igjennom tallene fra 1 til 9 og sjekker om tallet er et multiplum av 3 eller 5. Ved sånt tilfelle adderer vi tallet til summen. La oss skrive dette litt klarere, og for et vilkårlig tall \(N\) som øvre grense:

  • Gå igjennom heltallen fra \(1\) til \(N-1\) (for eksempel 9 om den øvre grensen er 10).

  • For hvert tall, sjekke om tallet er et multiplum av 3 eller 5.

  • Om det er det, addere tallet til summen.

Dette er algoritmen vår. Den er ikke avhengig av noe programmeringsspråk. Dette er bare et sett med instrukser som, om man utfører dem, gir summen fra problemet.

Implementasjon

Nå er det tid for del 2: å implementere algoritmen i Python. Her må man finne de delene av programmeringsspråket man skal bruke. I dette tilfelle vil vi gjøre noe for alle tall fra 1 til den øvre grensen (som vi gir navnet max_N). Dette kan vi for eksempel gjøre med en for-loop over range(max_N). Denne går fra og med null til og med max_N - 1:

max_N = 10
for n in range(max_N):

Vi må også definere summen før løkken (den er null fra begynnelsen), ellers har vi ingen summe å addere det første tallet til. Så vi lager en variabel summe som har verdien null, før for-løkken:

max_N = 10
summe = 0
for n in range(max_N):

For hver iterasjon av for-løkken skal vi sjekke om det aktuelle tallet n er et multiplum av 3 eller 5. Dette kan vi gjøre med et if-statement innen for-løkken. Her sjekker vi om tallet er deleligt med 3 eller 5 (det er samme som at det er et multiplum av 3 eller 5) med koden n % 3 == 0 or n % 5 == 0. Om det er det adderer vi tallet til summen med koden summe += n. Ellers skal vi ikke gjøre noe, så det er ingen mer kode innen for-løkken.

Her er koden. Du kan laste ned filen her: eksempel_problem_solving.py. Kjør koden og endre på max_N sånn at du kan svare på problemet. Hva er summen av alle multiplumer av 3 og 5 som er mindre enn 1000?

# Problemløsning
max_N = 10
summe = 0
for n in range(max_N):
    if (n % 3 == 0) or (n % 5 == 0):
        summe += n
print(f"The sum of all multiples of 3 or 5 below {max_N} is {summe}")

Hvordan kunne du svare på problemet med while-løkke i stedet av for-løkken?

Eksempel 6

Turtle module output: rotating square that scales up and changes color with each loop iteration

Som vi har sett tidligere uker, kan vi bruke Python-turtle for å forstå visse mønstre i Python. Denne ukens eksempel ser på turtle for å tegne former mer økonomisk med løkker.

Vi kan også inkludere farge-, skala- og rotasjonsendringer for å gjøre tegningene våre mer interessante.

Eksperimenter med filen her: turtle_loop.py. Hvilke mønstre kan du tegne?

import turtle as t

t.speed(0)
t.colormode(255)  # set color to rgb color mode (r,g,b)

sidelength = 20  # set length of side

for i in range(30):
    # update position and color
    t.right(15)
    t.color(255 - (3 * i), 132 - (3 * i), 190 - (3 * i))

    # draw basic square
    sides = 0
    while sides < 4:
        t.forward(sidelength + (5 * i))
        t.right(90)
        sides += 1

t.done()

Eksempel 7

Her er noen eksempler på kode som ikke er riktig.

Obs

Når vi bruker while-loops er det mulig å lage program som aldri stopper om vilkåret i while-løkken alltid er oppfylt. Om du tror at koden din er i en uendelig loop kan du avslutte kjøringen ved å taste ctrl-c i terminalen som kjører koden.

Last ned filen her: errors_1.py. Prøv først om du kan finne feilen i koden. Kjør siden koden og se hva som skjer. Endre koden slik at den går å kjøre, og ikke blir fast i en uendelig loop.

 1# typing CTRL-c in the terminal stops a running program
 2
 3print("Countdown:")
 4n = 10
 5while n > 0:
 6    print(n)
 7
 8
 9print("Countdown again:")
10n = 10
11while n > 0:
12    print(n)
13    n += 1
14
15print("Multiples of 3:")
16i = 30
17while i >= 0:
18    if i % 3 != 0:
19        continue
20        i -= 1
21    print(i)
22    i -= 1
23
24
25print("Find the sum of all numbers from 1 to 100")
26for n in range(1, 101):
27    sum = 0
28    sum += n
29print(sum)
30
31print("Again, find the sum of all numbers from 1 to 100")
32for n in range(1, 101):
33    sum_2 += n
34print(sum_2)

Obs

Det finnes mange gode eksempler og oppgaver i Automate the boring stuff, se for eksempel: «Practice Questions» 1.–13.

Obligatoriske oppgaver

Oppgave 1

Du finner Oppgave 1 nedenfor Eksempel 1.

Oppgave 2

Du finner Oppgave 2 nedenfor Eksempel 1.

Oppgave 3

Du finner Oppgave 3 nedenfor Eksempel 1.

Oppgave 4

Du finner Oppgave 4 nedenfor Eksempel 2.

Oppgave 5

Du finner Oppgave 5 nedenfor Eksempel 3.

Oppgave 6

Du finner Oppgave 6 nedenfor Eksempel 3.

Oppgave 7

Du finner Oppgave 7 nedenfor Eksempel 3.

Oppgave 8

Du finner Oppgave 8 nedenfor Eksempel 4.

Obs

Valg fra én av de neste tre større oppgavene (#9, #10 eller 11) å gjøre til denne uken. Du kan selvfølgelig gjøre alt, men bare én er obligatorisk.

Oppgave 9

I denne oppgaven skal vi beregne temperaturen av 250.0 mL vann som varmes opp fra romtemperatur (25.0°C) til kokepunktet (100.0°C). Begynne med tid = 0s.

For enkelhets skyld, anta at temperaturen til vannet øker lineært med \(0.625\,\mathrm{{}^\circ C} / \mathrm{s}\).

I filen uke_04_oppg_9.py skal du lage et programm som begynner med temperaturen 25.0°C og så printer ut for hver sekund temperaturen til vannet frem til det kokes på 100.0°C.

Til slutt skal programmet printe ut hvor lang tid det tok å nå kokepunktet med frasen 100.0°C i {resultat her} sekunder.

Vis resultatet til nærmeste tiende, men bruk full presision i beregningen, ikke bruk de rundete verdiene for å regne med videre. Enten f-string-formatering eller round() fungerer bra her.

0s = 25.0°C
1s = 25.6°C
2s = 26.2°C
3s = 26.9°C
4s = 27.5°C
...
...

100.0°C i ... sekunder

Oppgave 10

I denne oppgaven skal vi beregne posisjonen av en sten som droppes fra en høyde. I filen uke_04_oppg_10.py skal du lage et program som tar en høyde som input fra brukeren og så printer ut, for hver sekund, posisjonen av en sten som droppes fra den høyden, frem til stenen er på marken. Til sist skal programmet printe ut hvor lang tid det tok til stenen landet på marken.

Formelen for stenens posisjon \(p\) er følgende:

\[p = p_0 - \frac{1}{2} g t^2,\]

hvor \(p_0\) er stenens starthøyde, \(t\) er tiden som passert og \(g\) er tyngdakselerasjonen. I denne oppgaven bruker vi verdien \(9.8\,{\mathrm{m}}/{\mathrm{s}^2}\) for \(g\). Dette betyr altså at en sten som droppes fra høyden \(100\,\mathrm{m}\) etter \(4\,\mathrm{s}\) er på høyde \((100 - \frac{1}{2} \cdot 9.8 \cdot 4^2)\,\mathrm{m} \approx 21.6\,\mathrm{m}\).

Ditt program skal begynne med å skrive ut starthøyden (dette er da \(t=0\)). Så skal programmet skrive ut høyden hver sekund rundet til 1 desimal til og med stenen lander på marken på høyden \(0\,\mathrm{m}\). For eksempel, om stenen droppes fra høyde \(100\,\mathrm{m}\) skal ditt program skrive ut:

100 m
95.1 m
80.4 m
55.9 m
21.6 m
0 m

Den siste raden sier stenens høyde etter \(5\,\mathrm{s}\). Stenen lander på marken etter ca \(4.52\,\mathrm{s}\), men stanner selvfølgelig der etter det. Så skal derfor programmet skrive ut at den er på høyde \(0\) etter \(5\,\mathrm{s}\).

Programmet skal også beregne mellom vilke sekunder stenen lander på marken. I de fleste tilfellene kommer ikke stenen å lande på marken akkurat ved en hel sekund. Fra \(100\,\mathrm{m}\) lander den mellom \(4\) og \(5\,\mathrm{s}\) etter at den droppes. Da skal programmet skrive ut:

Stenen lander mellom 4 og 5 sekunder etter at den droppes.

Her er en eksempelkjøring på hele programmet:

Stenen droppes fra høyde: 1̲0̲0̲
100.0 m
95.1 m
80.4 m
55.9 m
21.6 m
0 m
Stenen lander mellom 4 og 5 sekunder etter at den droppes.

Oppgave 11

I filen uke_04_oppg_11.py skal du skrive et program som spør brukeren om et binært tall og siden skriver ut hvilket heltall det er (i basen 10).

For å skjønne hvordan binære tall er oppbygget kan vi se først på tall i basen 10 (som vi bruker til hverdags). Tallet \(4206\) deler vi opp som summen

\[\begin{split}\begin{matrix} 4 & & 2 & & 0 & & 6 \\ \downarrow & & \downarrow & & \downarrow & & \downarrow \\ 4 \cdot 10^3 & + & 2 \cdot 10^2 & + & 0 \cdot 10^1 & + & 6 \cdot 10^0. \end{matrix}\end{split}\]

Det første tallet sier hvor mange ganger vi tar \(1000 = 10^3\), det andre tallet sier hvor mange ganger vi tar \(100 = 10^2\), det tredje tallet sier hvor mange ganger vi tar \(10 = 10^1\) og det siste tallet sier hvor mange ganger vi tar \(1 = 10^0\).

Densamme formelen gjelder for binære strenger, med eneste forskjellen at vi velger potenser av \(2\) istedet for potenser av \(10\). Så det binære tallet \(110100\) er detsamme som summen

\[\begin{split}\begin{matrix} 1 & & 1 & & 0 & & 1 & & 0 & & 0 \\ \downarrow & & \downarrow & & \downarrow & & \downarrow & & \downarrow & & \downarrow \\ 1 \cdot 2^5 & + & 1 \cdot 2^4 & + & 0 \cdot 2^3 & + & 1 \cdot 2^2 & + & 0 \cdot 2^1 & + & 0 \cdot 2^0. \end{matrix}\end{split}\]

Så det betyr at \(110100\) representerer tallet \(32 + 16 + 4 = 52\).

Legg merke til at den høyeste potensen av \(2\) hører sammen med det første tallet og at verdien av denne potensen er avhengig av hvor langt det binære tallet er. For eksempel, \(110100\) er 6 tegn langt, så potensen av \(2\) tilsvarende \(1\) i starten er \(6-1 = 5\). Generellt så er potensen av \(2\) som tilsvarer det første tallet i et binært tall én mindre enn lengden av det binære tallet (akkurat som i bas 10).

Når man kjører programmet ditt skal det se ut slik som følgende eksempelkjøring:

Binært tall: 1̲1̲0̲1̲0̲0̲
52