Onsdag igen, idag kör vi ett lite speciellt avsnitt av…

Introduktion till programmering

Förra veckan avslutade vi avsnitten om sortering, denna vecka ska vi titta på programmet vi kommer använda för att avgöra vem som vinner dubbel rabatt i julkampanjen!

Vi kommer få in ett antal kommentarer på varje inlägg, och av dessa vill vi välja ut ett slumpmässigt inlägg. Jag tänker mig därför en funktion som tar ett invärde, som representerar antalet gissningar, och returnerar ett heltal som representerar det inlägg, i nummerordning, som har vunnit. Funktionen i sig är väldigt simpel och ser ut så här:

from random import randint

def chooseWinner (participants):
 return randint(1, participants)

Vi importerar randint (slumpmässigt heltal) från random-biblioteket. Denna funktion tar två invärden, lägsta tal och högsta tal, och returnerar ett tal som ligger mellan dessa två eller är like med ett av dem. Så i detta fall, om vi fått in 20 gissningar och skickar in det talet kommer vi få tillbaka ett tal mellan 1 och 20. Enkelt va?

Men nu uppstår ett problem. Hur vet vi att funktionen är rättvis? Hur vet vi att den inte slumpar fram samma tal nästan hela tiden? Här kommer testning in i bilden!

Testning

Testning är inget vi gått igenom tidigare, men det är väldigt viktigt inom programmeringen. Kort sagt kan man säga att man testar den kod man skrivit, för att försäkra sig om att den fungerar som önskat. Vad testar man? Egentligen vill man se till att funktionen kan hantera all möjlig data som kastas in i den. Om man t.ex. gör ett stort program som innehåller en metod som anropas från flera olika ställen i koden vill man försäkra sig om att metoden kan hantera all indata som den kan tänkas få, även den felaktiga. Annars kan hela programmet krascha om metoden inte är korrekt testad.

Hur testar man då? Jo, man försöker komma på all möjlig indata som funktionen kan tänkas få, och skapar fall för dessa. Dessa fall kan man sedan skapa funktioner för som anropar programmet man vill testa.

Nu är det bara vi här på m.nu som kommer använda funktionen, och eftersom jag skrivit koden vet jag att den inte kommer få fel indata. Vi kommer däremot att testa hur bra slumpningen är!

Hur går vi tillväga? Vi anropar funktionen massor av gånger, såklart! Vi anropar den med ett förbestämt tal som skulle kunna vara ett antal gissningar, och väljer 20 (jämnt, realistiskt och bra). Sen skapar vi en testfunktion där vi får ange hur många anrop vi vill göra till slumpfunktionen. Men vänta! Slumpfunktionen kanske beror på datorn interna klocka. Kan det innebära att man får bättre/sämre resultat genom att fördröja tiden mellan varje anrop? Vi testar!

Vi skapar därför en funktion som tar ett invärde, “antal iterationer”, och därefter kör två loopar efter varandra. Den första hämtar in ett tal mellan 1 och 20, fördröjer beräkningen i 0.0-1.0 sekunder, och gör sedan samma sak igen så många iterationer man angett. Den andra loopen gör samma sak men fördröjer inte, utan kör alla iterationer så fort den kan. Vi kommer därmed behöva random(), också den från random-biblioteket, samt sleep() från time-biblioteket:

from random import randint, random
from time import sleep

# Returns a random winner of x participants
def chooseWinner (participants):
 return randint(1, participants)

# Tests the chooseWinner for 20 participants
# at random and non-random intervals and saves data in array
def testChooser (iterations):
 i = 0
 randomIntervals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 normalIntervals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 while i < iterations:
  randomIntervals[chooseWinner(20)-1] += 1
  i += 1
  sleep(random())

 i = 0
 while i < iterations:
  normalIntervals[chooseWinner(20)-1] += 1
  i += 1

 print(randomIntervals)
 print(normalIntervals)

Men vänta nu! Vad händer här egentligen?

Först skapas två listor med 20 heltal vardera. Varje gång vi slumpar fram ett tal adderar vi ett till motsvarande position i listan (kom ihåg att listpositioner börjar på 0, därav “chooseWinner(20)-1”). Som jag nämnde tidigare slumpar random() fram ett flyttal mellan 0.0-1.0, så i den första loopen pausar vi upp till en sekund mellan varje iteration. Detta för att undersöka om det gör någon skillnad för funktionens förmåga att “rättvist” slumpa. Efter den andra loopen, som gör samma sak fast utan pauser, skrivs de båda listorna ut. Så vad har vi då fått ut för resultat? Jo, fördelningen av slumpade tal när man slumpar över 20 st deltagare! Och här är resultatet:

#>>> testChooser(1000)
#[53, 45, 48, 61, 54, 48, 45, 61, 43, 45, 46, 57, 51, 48, 39, 53, 59, 52, 49, 43]
#[58, 41, 57, 47, 47, 49, 52, 33, 59, 41, 51, 50, 43, 61, 57, 49, 48, 63, 49, 45]
#>>> testChooser(5000)
#[257, 242, 265, 228, 268, 255, 253, 245, 272, 245, 237, 275, 281, 235, 251, 255, 231, 210, 234, 261]
#[232, 235, 273, 248, 233, 255, 269, 252, 238, 252, 234, 259, 249, 269, 248, 256, 241, 244, 266, 247]

Här kör jag testet först på 1000 iterationer och sedan på 5000. Resultatet ser inte så orättvist ut, eller hur? Det går också att konstatera att tidsaspekten inte tycks ha gjort någon skillnad.

En annan sak som kan vara intressant att veta är att ju fler iterationer man kör, desto mindre blir skillnaden procentuellt mellan det tal som slumpas fram oftast och det som slumpats fram minst av alla. I fallet med 1000 iterationer är det minsta talet (39) 64% av det större talet (61), men för 5000 iterationer är 210 ~75% av 281.

Avslutningsvis

Detta är alltså programmet vi kommer använda för att avgöra vem som vinner dubbel rabatt i vår julkampanj. Hoppas inlägget var intressant, god jul!

Vi ses igen nästa vecka 😉