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
?
|
|
Istället kan vi använda en for
-loop. Syntaxen för for
-loopen är:
|
|
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:
|
|
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
).
|
|
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:
|
|
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]
så 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
:
|
|
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.
|
|
~ 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:
|
|
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:
|
|
Exempelvis:
|
|
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:
|
|
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
).
|
|
~ 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