Göm menyn

Extraövningar - Komma igång med programmering

För den som vill ha ytterligare mängdträning (eller för den delen fördjupning) utöver det som finns i själva labben.

Innehåll

E1.1 Fundamentala värden
E1.2 Jämförelser, if, else och elif
E1.3 for-loopar
E1.4 Funktioner och returvärden

E1.1 Fundamentala värden

Detta går lite djupare än uppgift 101 i laboration 1. Fundera på vad som händer innan du skriver in kod och testar. Det är bra att ha en hypotes först, och se om den stämmer (eftersom det ibland är lätt att förklara resultat i efterhand).

Extraövning E1.1-1 Skriv in följande rader vid Python-prompten, en i taget. Försök förutsäga vad a och b kommer att vara efter varje rad, och vad Python kommer att svara.

a = 0 b = 42 a + 1000 a - 2 b = a b = b + 15 a + 17

Svar Detta sker: a = 0, b = 42 a sätts till noll, b sätts till 42. a + 1000 returnerar 1000. a har fortfarande värdet 0. a - 2 ger -2. b = a. Värdet på a slås upp. b får detta värde. Man skapar ingen särskild koppling mellan a och b. b får samma värde som a råkade ha för tillfället. b = b + 15 Värdet 0 slås upp, 15 läggs till. b får detta nya värde. a är oförändrat. a + 17 Värdet 17 returneras. Varken a eller b förändras.

[Visa/dölj svar]

Extraövning E1.1-2 Skriv in följande rader vid Python-prompten, en i taget. Försök förutsäga vad a, b och c kommer att vara efter varje rad, och vad Python kommer att svara.

a = 42 a == 37 b = (a == a) c = a + 12 b == c

Svar a = 42, a får värdet 42. a == 37 a jämförs med 37. Är 42 samma som 37? False returneras (inget ändras). b = (a == a) b tilldelas värdet av (a==a)-jämförelsen. 42 == 42, så b har nu värdet True. c = a + 12 c får nuvarande värdet av a plus 12, så 54. b == c. Vi försöker undersöka om b och c har samma värde. True är inte 54, så vi returnerar False. Här är det värt att notera att man faktiskt får göra jämförelsen utan att något kraschar (trots att det ena är ett heltal, och det andra ett sanningsvärde).

[Visa/dölj svar]

Extraövning E1.1-3 Pröva nu följande rader. Vad förväntar du dig ska hända efter varje rad? Vilka värden har ändrats?

intro = "Hej" name = "Tesco" greeting = intro + " " + name greeting = greeting + " klockan är " greeting = greeting + 15 print(greeting) newgreeting = print(greeting)

Svar intro = "Hej", name = "Tesco", tilldela värdena (strängar). greeting = intro + " " + name. Slå upp värdet på intro, name och sätt samman. greeting får värdet "Hej Tesco" greeting = greeting + " klockan är ", greeting blir strängen "Hej Tesco klockan är ". greeting = greeting + 15, detta fungerar inte. Vi försöker lägga ihop en sträng (greeting) med ett heltal (15). print(greeting). Vi skriver ut strängen "Hej Tesco klockan är " på skärmen. Inget returvärde. newgreeting = print(greeting). Vi skriver ut strängen "Hej Tesco klockan är " på skärmen. Notera att newgreeting inte har något värde (undersök saken!)!

[Visa/dölj svar]

E1.2 Jämförelser, if, elif och else

Här kommer vi in på if-satsen, och förenklingar. Det här är viktigt att förstå ordentligt, eftersom elif som kommer i fel ordning kan vara en källa till svårupptäckta fel i din kod.

Extraövning E1.2-1 Är det skillnad mellan dessa två if-satser? Vad skiljer dem åt? Vilka olika värden på n ger vilka utskrifter?

Original:

if n < 0: print('Negativt') elif n < 1000: print('Litet') elif n > 1000: print('Stort') else: print('Mittemellan')

Omskrivning:

if n > 1000: print('Stort') elif n < 1000: print('Litet') elif n < 0: print('Negativt') else: print('Mittemellan')

Svar I originalet får vi svaren. n < 0 - Negativt. 0 <= n < 1000 - Litet. n == 1000 - Mittemellan. n > 1000 - Stort. Den som kanske behöver förklaras är "Mittemellan". Där kan man tänka så här: när vi har kommit till else-grenen, vet vi - att n inte är mindre än 0 (då hade vi fastnat i översta delen), - inte mindre än 1000 (då hade vi fastnat vid elif n < 1000), - och inte större än 1000. Det enda möjliga värdet på n blir då precis 1000. I omskrivningen får vi: n > 1000 - Stort. n < 1000 - Litet. n == 1000 - Mittemellan. Varför? Om n är negativt, så är det dessutom mindre än 1000. Eftersom vi testar om talet är mindre än 1000 innan vi testar om det är negativt, så kommer alla negativa tal resultera i att man skriver ut "Litet". Negativt-delen kunde lika gärna tagits bort.

[Visa/dölj svar]

Extraövning E1.2-2 Skriv om detta utan nästlade if-satser (if-sats-i-if-sats...). För vilka n (där n är heltal) skriver vi ut de olika meddelandena?

if n > 0: if n > 100: print('Stort!') else: if n <= 50: print('Medelstort') else: print('Lite större') else: print('Jättelitet')

Svar

if n > 100: print('Stort!') elif n > 50: print('Lite större') elif n > 0: print('Medelstort') else: print('Jättelitet')

OBS! Det finns flera korrekta sätt att skriva detta på. Det viktiga här är att man tänker på vilka möjligheter man uteslutit. När vi har kommit ned till andra elif-raden (elif n > 0), vet vi att n inte kan vara större än 100. Då hade vi fastnat och skrivit ut "Stort". Det kan inte heller vara större än 50, för då hade vi skrivit ut "Lite större". n är alltså mindre än eller lika med 50. Om vi skriver ut "Medelstort" vet vi alltså både att n <= 50 (notera att n = 50 fungerar!), och att n > 0. (Här använder vi dessutom att n är ett heltal. Om det inte varit angivet att det var ett heltal, skulle man ju kunna ha med till exempel n = 50.5.)

[Visa/dölj svar]

Extraövning E1.2-3 En lite större övning i att förenkla. Detta något krångliga uttryck kan reduceras till 6 rader (utan if-sats-i-if-sats). Gör det. n är här ett heltal. Börja med att lista ut vilka n som är tillåtna i varje alternativ (till exempel 100 < n < 1000), och reducera utifrån det.

if n > 10: if n > 100: if n < 1000: print(n) else: if n < 1001: print('Rätt stort') else: print(n) else: if n == 11: print(n) else: if n == 10: print('10!') else: print(n) else: if n < 0: print('Under noll') else: print(n)

[Visa/dölj lösningsgång (steg 1)]

Svar Vi börjar med att anteckna vad n kan vara i de olika fallen (här är det hanterligt att göra det för alla alternativ på en gång).

if n > 10: if n > 100: if n < 1000: print(n) # <-- 100 < n < 1000 else: if n < 1001: print('Rätt stort') # <-- 1000 <= n < 1001, så exakt n == 1000 else: print(n) # <-- 1001 <= n else: if n == 11: print(n) # <-- 10 < n <= 100 och dessutom: n är 11 else: if n == 10: print('10!') # <-- Omöjligt! else: print(n) # <-- 10 <= n <= 100, och n är inte 11 else: if n < 0: print('Under noll') # <-- n < 0 else: print(n) # <-- 0 <= n <= 10

Varför är det fetstilta alternativet omöjligt? Om vi har kommit ned dit, vet vi två saker: att inte n > 100 (då hade vi hamnat i grenen ovanför) och att n > 10, eftersom vi inte är i sista delen av programmet. Se på indragen i koden! Vi kan alltså skriva om

if n == 10: print('10!') else: print(n)

till enbart raden print(n). Kan du använda detta för att förkorta ytterligare?

[Visa/dölj lösningsgång (steg 2)]

if n > 10: if n > 100: if n < 1000: print(n) # <-- 100 < n < 1000 else: if n < 1001: print('Rätt stort') # <-- 1000 <= n < 1001, så exakt n == 1000 else: print(n) # <-- 1001 <= n else: if n == 11: print(n) # <-- 10 < n <= 100 och dessutom: n är 11 else: print(n) # <-- Ersatt n==10-jämf. else: if n < 0: print('Under noll') # <-- n < 0 else: print(n) # <-- 0 <= n <= 10

Vi ser att om 10 <= n <= 100 så spelar det ingen roll om n är 11 eller inte, utfallet blir detsamma. Vi ersätter hela if-satsen.

if n > 10: if n > 100: if n < 1000: print(n) # <-- 100 < n < 1000 else: if n < 1001: print('Rätt stort') # <-- Exakt n == 1000 else: print(n) # <-- 1001 <= n else: print(n) # <-- Ersatt n==11-jämf. 10 <= n <= 100 else: if n < 0: print('Under noll') # <-- n < 0 else: print(n) # <-- 0 <= n <= 10

Det ser ut som att print(n) sker rätt ofta. Kan man identifiera när något annat händer? .

[Visa/dölj lösningsgång (steg 3)]

Vi ser att samma sak sker för alla n utom i fallen då - n är precis 1000 (n är ett heltal, n >= 1000 och dessutom n < 1001.) - n är mindre än 0. Skriv om detta som en if-sats. Facit nedan.

[Visa/dölj facit]

Svar Vi kan skriva om det till

if n < 0: print('Under noll') elif n == 1000: print('Rätt stort') else: print(n)

E1.3 for-loopar

for-loopar är rätt grundläggande i imperativ programmering. Här testas både loopar där man räknar upp tal (iteration över en range) sådana där man arbetar direkt med elementen i listor. Ibland är det praktiskt att iterera över en indexmängd, säga att vi ska ta ut ett element på plats i, och ibland att iterera direkt över listan. Här tränas detta ytterligare.

Fler iterationsövningar följer i nästa avsnitt, Funktioner.

Extraövning E1.3-1 Du har ett tal m och ett (större) tal n. Skriv en loop som skriver ut alla tal m, ..., n.

>>> print_range(1, 5) 1 2 3 4 5

[Visa/dölj svar]

Extraövning E1.3-2 Vi har skrivit följande program. Tanken är att skriva ut summan av alla tal från 0 till 5. Vad kommer programmet att skriva ut på skärmen? Vad har gått fel, och hur kan det lagas?

n = 0 for i in range(6): n + i print(n)

Svar Det som skrivs ut är 0, eftersom vi aldrig uppdaterar n. Programmet gör 5 "varv". Första varvet är n = 0, i = 0 och vi lägger ihop dem och returnerar värdet 0 (ut i tomma intet). Andra varvet är n = 0, i = 1 och vi lägger ihop dem och returnerar värdet 1 (återigen ut i tomma intet). Så fortsätter det. Lösningen blir att ändra programmet till:

n = 0 for i in range(6): n = n + i print(n)

[Visa/dölj svar]

Extraövning E1.3-3 Tidigare har vi itererat över ranges. Nu prövar vi att iterera direkt över listor. Du ges följande lista av årtal:

disc_years = [1994, 1996, 1998, 2000, 2002, 2006, 2011]

Nu vill du skriva ut hur många år sedan var och en av dessa inträffade. Skriv en loop som går igenom disc_years med hjälp av index (första varvet tar vi ut disc_years[0], andra varvet disc_years[1],...). Skriv också en version som inte använder index.

Svar Vi använder index. Vi har 7 element i listan, så index går från 0 till 6. Här använder vi len(disc_years) för att få fram längden (length) på listan.

for i in range(len(disc_years)): print(2013 - disc_years[i])

I första varvet av loopen är i=0, och vi tar ut disc_years[0] (siffran 1994). I andra varvet av loopen är i=1, och vi tar ut disc_years[1] (siffran 1996). ... Finlir: vi har totalt len(disc_years) = 7 element i listan. Varför hamnar vi inte utanför listan? Prövar man att ta ut disc_years[7], kraschar det. Svaret är att range(7) inkluderar de sju talen 0, 1, ..., 5, 6 (men inte 7).

[Visa/dölj svar med index]

Svar Utan index är det ännu enklare:

for current_year in disc_years: print(2013 - current_year)

Notera att current_year inte på något sätt anger var i listan man hittar årtalet. Det är helt enkelt innehållet på den nuvarande platsen. Första varvet är current_year 1994. Andra varvet är current_year 1996. ...

[Visa/dölj svar utan index]

Extraövning E1.3-4 Ibland är det praktiskt att iterera direkt över listor, ibland att använda index. Du får nu följande:

disc_years = [1994, 1996, 1998, 2000, 2002, 2006, 2011] disc_titles = ['Dreams of a cryotank', 'Sequencer', 'Europa', 'United States of Mind', 'Northern Light', 'Skyshaper', 'Modern ruin']

Gör ett program som skriver ut enligt:

År 1994 släpptes Dreams of a cryotank. År 1996 släpptes Sequencer. ...

Svar Här vill vi gå igenom två lika långa listor, och para ihop första elementet i den ena (årtal) med första elementet i andra (skivtitel). Då kan index vara praktiskt:

for i in range(len(disc_years)): current_disc_year = disc_years[i] current_disc_title = disc_titles[i] print('År', current_disc_year, 'släpptes', current_disc_title, '.')

Här hade vi (för tydlighets skull) en current_disc_year och current_disc_title som gäller för varje varv. Egentligen är det onödigt. Vi kan använda värdena direkt (och inte skapa skräpvariabler):

for i in range(len(disc_years)): print('År', disc_years[i], 'släpptes', disc_titles[i], '.')

[Visa/dölj svar]

E1.4 Funktioner och returvärden

Extraövning E1.4-1 Skriv en procedur sumrange som returnerar summan av alla tal från 0 till n.

>>> sumrange(5) 15

Svar

def sumrange(n): sum = 0 for i in range(n): sum = sum + n return sum

Notera särskilt return-delen på sista raden. Den gör att man inte bara räknar ut summan, utan också ger tillbaka den.

[Visa/dölj svar]

Extraövning E1.4-2 På föreläsningen gick vi igenom proceduren find_root. Den ser ut så här:

def find_root(): print('This program attempts to find the square root of a number.') x = eval(input('Enter a number: ')) guess = x / 2 for i in range(5): guess = (guess + x/guess) / 2 print(guess)

Skriv ett program rootloop som tar en siffra n och kör programmet ovan n gånger.

>>> rootloop(2) This program attempts to find the square root of a number. Enter a number: 25 ... This program attempts to find the square root of a number. Enter a number: 49 ...

Svar

def rootloop(n): for i in range(n): find_root()

[Visa/dölj svar]

Extraövning E1.4-3 Skriv ett program mean_roots som tar en siffra n, kör find_root n gånger och därefter skriver ut medelvärdet av kvadratrötterna. Går det? Om inte, finns det något enkelt sätt att antingen ändra i find_root så att det fungerar?

>>> mean_roots(2) This program attempts to find the square root of a number. Enter a number: 25 7.25 5.349137931034482 5.011394106532552 5.000012953048684 5.000000000016778 This program attempts to find the square root of a number. Enter a number: 49 13.25 8.474056603773585 7.128205591060185 7.001152932064677 7.000000094930961 The mean of the roots is: 6.000000047473869

Svar Nej, det går inte direkt. find_root skriver bara ut svaren på skärmen, de ger aldrig tillbaka en siffra som datorn kan behandla. Lösningen blir att lägga till en return-rad i programmet.

def mean_roots(n): rootsum = 0 for i in range(n): rootsum += find_root_mod() print ('\nThe mean of the roots is:', (rootsum / n)) def find_root_mod(): print('This program attempts to find the square root of a number.') x = eval(input('Enter a number: ')) guess = x / 2 for i in range(5): guess = (guess + x/guess) / 2 print(guess) return guess # <------ Ny rad

[Visa/dölj svar]

Extraövning E1.4-4 Skriv ett program oddsum_loop(n) som ber om n heltal. När talen matats in ger den tillbaka summan av alla de udda tal som matats in. Nedan får du en funktion isodd(n) som ger True om ett tal är udda, och False annars (ett s k predikat).

def isodd(n): return n % 2 == 1

Den matematiskt lagde kan notera att % betyder "rest". Så vi säger att ett heltal n är udda om man får resten 1 när man delar talet med 2.

>>> oddsum_loop(3) Enter a number: 5 Enter a number: 123 Enter a number: -101 The sum of the odd numbers is: 27

Svar Vi vill göra något n gånger, så en loop över range(n) låter lämpligt. I varje varv ska användaren få mata in ett tal. Det är bara om talet är udda som det är intressant. Vi får alltså ett villkor (så ett if-statement). Det som ska hända om talet är udda är att vi ska lägga till det till en summa.

def oddsum_loop(n): odd_sum = 0 for i in range(n): current = eval(input('Enter a number:')) if isodd(current): odd_sum = odd_sum + current print('The sum of the odd numbers is:', odd_sum)

Det som vi är intresserade av är odd_sum-variabeln. Tänk efter när den uppdateras.

[Visa/dölj svar]

Extraövning E1.4-5 Vad kommer följande program att returnera? Pröva att skriva in det och jämför din förutsägelse med resultatet. Hur förklarar du resultatet?

def sum_first(n): sum = 0 for i in range(n): sum = sum + i return sum return sum >>> sum_first(5)

Svar Programmet returnerar 0. return bryter loopen och gör att programmet avslutas. Därför hinner det inte längre än första varvet i for-loopen.

[Visa/dölj svar]

Extraövning E1.4-6 Skriv ett program sumodd_range(m , n) som går genom alla tal från m, m+1, ..., n-1, n och returnerar summan av alla udda tal. När den stöter på ett jämnt tal ska den skriva ut det på skärmen.

>>> sumodd_range(5, 10) 6 is even! 8 is even! 10 is even! 21

Svar Här behöver vi göra något ett antal varv, så for kan vara lämpligt. Denna gång spelar det roll var vi börjar och slutar, så vi itererar över en range(m, n+1). Notera särskilt n + 1. Anledningen till det är att range går från första talet sista talet, men utan att inkludera den övre gränsen. Vad vi ska göra beror på om talet är udda eller ej, så vi använder ett if-statement med både villkors- och alternativgren.

def sumodd_range(m, n): sum = 0 for i in range(m, n+1): if isodd(i): sum = sum + i else: print(i, 'is even!') return sum

[Visa/dölj svar]

Extraövning E1.4-7 Skriv ett program sum_below(m, n, cutoff) som ger summan av alla tal som ligger under cutoff. Du kan räkna med att cutoff är större än m. För tydlighets skull (under utvecklingen) ska programmet dessutom skriva ut alla tal den lägger till, så att man ser att det blir rätt. Fundera på om ditt program gör något i onödan. (Du får inte använda den matematiska formeln.)

>>> sum_below(5, 10, 7) Added 5 Added 6 11 >>> sum_below(1, 3, 17) Added 1 Added 2 Added 3 6

Svar Återigen gör vi något ett antal varv, och har ett villkor. Vi räknar upp talen m, m+1, ..., n-1, n (notera gränsen!). Om det tal vi är på just nu är mindre än cutoff, lägg till det.

def sum_below_naiv(m, n, cutoff): sum = 0 for i in range(m, n+1): if i < cutoff: print('Added', i) sum = sum + i return sum

Men, notera att vi i lösningen går igenom alla tal från m till n+1, även de vi aldrig lägger till. Det finns ett fiffigare sätt. Vilket?

[Visa/dölj enklaste svaret]

[Visa/dölj förbättrade svaret]

Svar Här förbättrar vi koden lite, så att vi inte fortsätter räkna onödiga tal.

def sum_below_better(m, n, cutoff): sum = 0 for i in range(m, n+1): if i < cutoff: print('Added', i) sum = sum + i else: return sum return sum

Vi kontrollerar alltså om vi har räknat över maxgränsen cutoff (else-grenen), och avbryter isåfall. För den som verkligen vill ägna sig åt finlir finns ytterligare en förbättring. Här ovan gör vi en del onödiga jämförelser. Hur skulle man kunna undvika detta?
Svar Vi bestämmer slutpunkten först. Sedan gör vi beräkningarna. Det här gör att vi inte i varje varv behöva kolla om vi har tagit oss över gränsen.

def sum_below(m, n, cutoff): sum = 0 if cutoff >= n: endpoint = n + 1 else: endpoint = cutoff for i in range(m, endpoint): print('Added', i) sum = sum + i return sum

Den här lösningen ligger närmast den matematiska formeln (ta reda på start- och slutpunkt, räkna ut genomsnitt, multiplicera). Om vi lättat på kravet på "Added"-spårutskrifter, skulle man kunna gå direkt från den här lösningen till den matematiska (som inte kräver några varv i en for-loop). Det bör sägas att man inte vinner jättemycket på den här förbättringen (relativt den ovan). Men det är bra att börja tänka på.

[Visa/dölj förbättrade svaret II]

Extraövning E1.4-8 Skriv ett predikat isoldschool(year) som ger True om year ligger strikt mellan 1902 och 1986, och False annars.

Svar Ett första försök vore

def isoldschool(year): if year > 1902: if year < 1986: return True else: return False else: return False

Här kan vi använda lite logik för att reducera det en nivå:

def isoldschool(year): if year > 1902 and year < 1986: return True else: return False

I Python hade 1902 < year < 1986 också fungerat. Det här bryter mot en enkel och grundläggande regel: gör inte if-satser i onödan. Vi testar med year > 1903 and year 1986. Det ger True eller False som svar (pröva!). Om svaret är True, så går vi in i en gren där vi ger... True. Om det är False, går vi in i en gren där vi returnerar False. Vi kunde alltså lika gärna returnera värdet som kommer tillbaka från själva jämförelsen. Lösningen bör därför se ut något i stil med:

def isoldschool(year): return year > 1902 and year < 1986

[Visa/dölj svar]


Sidansvarig: Peter Dalenius
Senast uppdaterad: 2021-12-03