Göm meny
Gäller för: HT25

Förberedelsematerial 2.2

Inför seminariet ska du gå igenom materialet nedan, vilket bör ta ca en timme, samt genomföra det tillhörande quizet som testar dina kunskaper på materialet. Quizet finns endast till för att du ska kunna skatta dina egna kunskaper inför seminariet, och är inget du bedöms på eller ens sparas.

Innehåll

Iteration

I storseminarie 2.1 arbetade vi igenom en lista med hjälp av rekursion. Vi stegade, eller traverserade, igenom listan med namn och för varje rekursivt anrop utförde vi en operation på det första elementet.

Detta fungerar på relativt korta listor (under 1 000 element i normala fall). En nackdel med rekursion är dock att vi gör väldigt många funktionsanrop i funktionsanrop, som alla hamnar på den så kallade anropsstacken. Denna har en maximal storlek och det innebär att vi också bara kan hantera en maximal mängd element i en sekvens på det här sättet.

Olika språk och programmeringsparadigm (sätt att bygga program) löser det här problemet på olika sätt. Det vanligaste sättet och sättet vi kommer fokusera på här är att istället för rekursion använda iteration.

Python har två huvudsakliga konstruktioner för att utföra iteration, for och while.

Iteration över en sekvens med for

Se exemplet nedan

99 bottles of beer on the wall, 99 bottles of beer.
Take one down, pass it around. 98 bottles of beer.
98 bottles of beer on the wall, 98 bottles of beer.
Take one down, pass it around. 97 bottles of beer.
97 bottles of beer on the wall, 97 bottles of beer.
Take one down, pass it around. 96 bottles of beer.
96 bottles of beer on the wall, 96 bottles of beer.
Take one down, pass it around. 95 bottles of beer.
95 bottles of beer on the wall, 95 bottles of beer.
Take one down, pass it around. 94 bottles of beer.
94 bottles of beer on the wall, 94 bottles of beer.
Take one down, pass it around. 93 bottles of beer.
93 bottles of beer on the wall, 93 bottles of beer.
Take one down, pass it around. 92 bottles of beer.
...
...

Om vi vill skapa ett program som genererar denna utskrift men börjar med 1 500 flaskor så kommer vår rekursiva traverseringsmetod inte att fungera. Kopiera koden nedan och testa med olika argument till song_rec. Ungefär hur stora värden kan man skicka in utan att programmet ger ett RecursionError?

1
2
3
4
5
6
7
def song_rec(i):
   if i > 0:
       print(f"{i} bottles of beer on the wall, {i} bottles of beer")
       print(f"Take one down, pass it around, {i-1} bottles of beer on the wall")
       song_rec(i - 1)

song_rec(100)

Istället kan vi använda en for-loop. Syntaxen för for-loopen är:

1
2
for loop_variable in iterable_data:
    ... # Gör något med loop_variable som kommer representera varje element i iterable_data

Till skillnad från vår tidigare, rekursiva, traversering så kallar vi inte på en funktion i varje steg och begränsas därför inte heller av storleken på anropsstacken. Istället upprepas kodblocket tillhörande for-satsen en gång för varje element i iterable_data. För varje upprepning tilldelas loop_variable värdet av nästa element i iterable_data, som kan vara vilken typ av data som helst som är iterabel.

Vilka datatyper detta faktiskt är kommer du få en känsla för, men av de datatyper vi stött på hittills är det strängar (str), listor (list) och tupler (tuple). Mer generellt kan man säga att alla datatyper som är sekvenser är iterabla.

Exempelvis:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
song_lines = [
    "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down, pass it around. 98 bottles of beer.",
    "98 bottles of beer on the wall, 98 bottles of beer.\nTake one down, pass it around. 97 bottles of beer.",
    "97 bottles of beer on the wall, 97 bottles of beer.\nTake one down, pass it around. 96 bottles of beer.",
    "96 bottles of beer on the wall, 96 bottles of beer.\nTake one down, pass it around. 95 bottles of beer.",
    "95 bottles of beer on the wall, 95 bottles of beer.\nTake one down, pass it around. 94 bottles of beer.",
    "94 bottles of beer on the wall, 94 bottles of beer.\nTake one down, pass it around. 93 bottles of beer.",
    "93 bottles of beer on the wall, 93 bottles of beer.\nTake one down, pass it around. 92 bottles of beer.",
]


for line in song_lines:
    print(line)

Resultatet kommer motsvara texten i exemplet ovan.

Detta leder dock till ett litet problem: Om vi vill börja med 1 500 flaskor så behöver vi göra en lista med 1500 element, men det finns ett bättre sätt.

Vi fokuserar på själva siffrorna eftersom det är det enda som förändras mellan verserna i sången.

Datatypen range

Datatypen range representerar en av sekvens av heltal från ett startvärde (0, om inget annat anges) till ett slutvärde (som skickas som argument till range).

1
range(1500)

Anropet ovan ger oss en sekvens (som en lista eller tupel) med 1500 värden, från 0 till 1499. Vi kan sedan iterera över denna sekvens och använda dess element hur vi vill. Stoppvärdet är exklusivt (jämför med index-värden i en sekvens). Vi vill därför använda argumentet 1501 för att få alla värden från 0 till och med 1500:

1
2
for number in range(1501)
    print(number)
0
1
2
3
4
.
.
.
1499
1500

Vi ville ju egentligen ha heltalen från 1500 ner till 1 men vad vi har åstadkommit är heltalen från 0 upp till 1500. Den som är lite finurlig ser kanske flera sätt att lösa detta.

Dels kan vi med enkel aritmetik kan översätta sekvensen vi fick till sekvensen vi vill ha. Dvs. vi kan ändra inne i loopkroppen från print(number) till print(1500-number).

Vi vet ju också att range är en sekvens, så vi kan använda subskriptnotationen som vi sett för strängar, listor och tupler för att göra ett utsnitt. Vi kan vända på en sekvens med hjälp av subskriptet [::-1]range(1501)[::-1] kommer motsvara den sekvens vi vill ha.

Båda dessa approacher är dock otympliga i jämförelse med att säga åt range att ge oss rätt sekvens direkt. Vi slår upp range i dokumentationen med hjälp av funktionen help:

1
>>> help(range)
class range(object)
 |  range(stop) -> range object
 |  range(start, stop[, step]) -> range object
 |
 |  Return an object that produces a sequence of integers from start (inclusive)
 |  to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
 |  start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
 |  These are exactly the valid indices for a list of 4 elements.
 |  When step is given, it specifies the increment (or decrement).
 . 
 . 
 .

På andra och tredje raderna ser vi två olika sätt att anropa range och på rad 5-9 förklaras hur argumenten tolkas. Skickar vi bara med ett argument blir det automatiskt stoppvärde och startvärdet blir 0, men skickar vi med 2 eller 3 argument kan vi vara mer flexibla. Vad dokumentationen betyder är att vi kan skriva range(1500, 0, -1) för att få heltalen från 1500 till 1 med ett steg i negativ riktning för varje element.

1
2
3
for number in range(1500, 0, -1):
    print(f"{number} bottles of beer on the wall, {number} bottles of beer.")
    print(f"Take one down, pass it around. {number-1} bottles of beer.")

~ Testa gärna själv och se vad som händer! ~

En mer generell version av skriptet ovan får vi genom att skapa en funktion:

1
2
3
4
5
6
def song(number_of_verses):
    for number in range(number_of_verses, 0, -1):
        print(f"{number} bottles of beer on the wall, {number} bottles of beer.")
        print(f"Take one down, pass it around. {number-1} bottles of beer.")

song(99)

Vi har sett att for-loopen fungerar jättebra när vi har en sekvens att iterera över eller kan specificera en sådan sekvens med hjälp av range.

Ibland kan vi dock inte säga i förväg hur många gånger ett stycke kod behöver upprepas. I de fallen behöver vi ett mer generellt sätt att åstadkomma upprepning. Vi har sett att rekursion är ett sådant sätt, men att det inte är lämpligt för långa sekvenser.

Generell iteration med while

Vi kan se while-loopen som en generalisering av villkorssatsen if. Istället för att bara utföra koden i det tillhörande blocket en gång, om villkoret är sant, så upprepas koden i blocket så länge som villkoret är sant. Med andra ord kan vi läsa ut det som “så länge som sanningsuttrycket är sant utför följande”.

Syntaxen för en while-loop ser ut som följande:

1
2
3
4
5
while <uttryck som beräknas till ett sanningsvärde>:
    sats1
    sats2
    sats3
    ...

Exempelvis:

1
2
while True:
    print("It never ends!")

Denna loop kommer fortsätta för evigt om vi inte avbryter den manuellt. Vi kallar det för en oändlig loop och det är en mycket vanlig bugg, speciellt när man är nybörjare.

Hamnar du i situationen att ditt program stannar och aldrig kommer vidare är det troligt att du råkat skapa en oändlig loop. Tryck i så fall “Ctrl+C” för att avbryta loopen. Man kan också använda “Ctrl+D” för att avbryta en process i ett terminalfönster.

För att undvika oändliga loopar vill vi i första hand använda for när det går eftersom vi i det fallet nästan alltid kan vara säkra på att loopen kommer avslutas. Är det inte möjligt så får vi skriva vårt villkor på ett sådant sätt att det förr eller senare blir falskt.

Ett vanligt exempel som upprepar något exakt 10 gånger:

1
2
3
4
counter = 0
while counter < 10:
    ...
    counter = counter + 1

Denna loop kommer utföra loopkroppen 10 gånger och sen gå vidare när värdet på counter blir lika med 10 eller mer.

~ Testa gärna själv genom att skriva print("The counter is " + counter) istället för .... ~

Avbryta eller hoppa över varv

Vi kan hoppa direkt till nästa iteration utan att slutföra loopblocket genom att använda nyckelordet continue. Detta avbryter inte loopen, men eventuella satser efter continue i loopblocket kommer alltså inte att utföras för den aktuella iterationen.

Vi kan också avbryta en pågånde iteration helt med hjälp av nyckelordet break (vi kan också returnera inne i en loop, vilket automatiskt avbryter loopen iom att hela funktionen avslutas vid return).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
count = 0
while count < 10:
    if count == 3 or count == 5:
        count += 1
        continue

    if count > 7:
        print("Nu orkar jag inte mer." + "(count: " + str(count) + ")")
        break
    elif count > 3:
        print("Är vi framme snart?" + "(count: " + str(count) + ")")
    elif count > 4:
        print("Har vi kört vilse?" + "(count: " + str(count) + ")")
    else:
        print("Det här är roligt!" + "(count: " + str(count) + ")")
    count += 1

~ Titta på koden och försök att räkna ut vad som kommer skrivas ut. Testkör sedan koden. Hade du rätt? Om inte, varför blev det inte som du tänkt? ~

För den nyfikne

På Youtube: The Fastest Way to Loop in Python - An Unfortunate Truth

Quiz

Testa din förståelse av materialet med tillhörande quiz.


Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2024-07-26