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

Storseminarium 2.2 - En sak i taget

Innan seminariet ska du ha gått igenom Inför seminariet nedan och gjort tillhörande quiz. Syftet med detta är att du ska bekanta dig med innehållet så eventuella frågor kan redas ut under seminariet.

Denna sida visar en del av det som kommer att diskuteras på seminariet. Det kan hända att handledarna också tar upp andra uppgifter som inte behöver något specifikt studiematerial och då syns dessa uppgifter inte på sidan.

Inför seminariet

Under seminariet

Vi går igenom nedanstående uppgifter tillsammans, det kommer finnas möjlighet till diskussion och frågor. Känn er fria att följa med på uppgifterna på egen dator under seminariet om ni lär er bättre så, men vi kan inte garantera att vi har tid att vänta in alla.

Skriv ut tal

a)

Skriv en funktion print_numbers(first, last) som skriver ut alla heltal mellan first och last. Om first är 3 och last är 5 så ska alltså talen 3, 4 och 5 skrivas ut. Antag att last är större än first. Lös med rekursion, med for och med while.

(Eftersom det inte är specificerat i uppgiften får ni gärna demonstrera olika sätt att printa genom att använda end-parametern till print, t.ex. skriva ut på samma rad genom att använda end=" " som i exemplen nedan.)

Använd Pythontutor för att stega igenom lösningar om ni är osäkra.

1
2
3
4
5
6
def print_numbers_rec(first, last):
    if first > last:
        print() # Newline after finishing
        return
    print(first, end=' ')
    print_numbers_rec(first+1, last)
1
2
3
4
def print_numbers_for(first, last):
    for i in range(first, last+1):
        print(i, end=' ')
    print() # Newline after the loop
1
2
3
4
5
6
def print_numbers_while(first, last):
    i = first
    while i <= last:
        print(i, end=' ')
        i = i + 1
    print() # Newline after the loop

Vilken lösning är lättare att förstå? Vilka skäl finns det för att föredra en viss lösning före en annan?

Poängtera att det i while-fallet finns 3 olika ställen att göra fel med hänsyn till själva upprepningslogiken (rad 2, 3 och 5), i rekursionsfallet finns 2 (rad 2 och 6) medan i for-fallet är det bara rad 2 som är avgörande för upprepningslogiken. Det är också bra att poängtera att for-loopen, iom att den har mer av ett “standard”-beteende ofta är lättare att tolka. Vi vet att vi har en sekvens och vi vet att vi ska göra något med varje element i den sekvensen, det skär av väldigt många potentiella andra typer av upprepning.

b)

Generalisera lösningarna så att funktionen fungerar även om first är större än last. Om first är 9 och last är 6 så ska alltså talen 9, 8, 7 och 6 skrivas ut. Förändrar detta vilken lösning som är “bäst”?

Det finns självklart många sätt att lösa detta, här är bara några stycken.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def print_numbers_rec(first, last):
    if first == last:
        print(first) # Newline on last print
        return
    print(first, end=' ')
    # Take one recursive step toward the base case
    if first > last:
        return print_numbers_rec(first - 1, last)
    else:
        return print_numbers_rec(first + 1, last)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def print_numbers_for(first, last):
    # Determine if decreasing or increasing
    if first > last:
        step = -1
    else:
        step = 1
    # Perform loop
    for i in range(first, last+step, step):
        print(i, end=' ')
    print() # Newline after the loop
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def print_numbers_while(first, last):
    # Determine if decreasing or increasing
    if first > last:
        step = -1
    else:
        step = 1
    # Perform loop
    i = first
    while i != last:
        print(i, end=' ')
        i = i + step
    print(last) # Newline on last print

Multiplikationstabell

a)

Skriv en funktion multiplication_table(n, last_factor) som skriver ut multiplikationstabellen för talet n upp till och med last_factor. Lös med lämplig loop.

Exempel

>>> multiplication_table(7, 4)
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28

En for-loop är lämpligast eftersom vi vet vilka faktorer vi ska multiplicera med och kan specificera det i förväg som en range:

1
2
3
def multiplication_table(n, last_factor):
    for i in range(1, last_factor + 1):
        print(f"{n} x {i} = {n * i}")

b)

Skriv en funktion multiplication_table(n, max_product) som skriver ut multiplikationstabellen för talet n upp tills max_product överskrids. Lös med lämplig loop.

Exempel

>>> multiplication_table(7, 30)
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28

Detta är ett gränsfall där både for och while kan betraktas som lämpliga.

Vi kan ta reda på den största faktorn som skall ingå med hjälp av heltalsdivision och lösa uppgiften med for:

1
2
3
def multiplication_table_for(n, max_product):
    for i in range(1, max_product // n + 1):
        print(f"{n} x {i} = {n * i}")

Är beräkningen lite mer komplicerad kan det dock vara svårt och en lämpligare approach är i så fall att använda while:

1
2
3
4
5
def multiplication_table_while(n, max_product):
    i = 1
    while n * i <= max_product:
        print(f"{n} x {i} = {n * i}")
        i += 1

Vi kan också utnyttja förhållandet vi lärt oss i rekursionskapitlet, att multiplikation kan uttryckas som upprepad addition. Här har vi gått från att beräkna två multiplikationer i varje iteration till att beräkna en addition. På moderna datorer och i enkla fall som detta spelar det i princip ingen roll, men det fanns en tid (och troligtvis finns fortfarande omständigheter), då detta gjorde stor skillnad.

1
2
3
4
5
6
7
def multiplication_table_while2(n, max_product):
    product = n
    i = 1
    while product <= max_product:
        print(f"{n} x {i} = {product}")
        i = i + 1
        product = product + n

Gissa tal

a)

Skriv en funktion guess_the_number(lowest, highest) som slumpar ett tal mellan lowest och highest och låter användaren gissa tills hen gissat rätt. Vi kan ta input från användaren med hjälp av funktionen input(prompt).

OBS! Tänk på att värdet som slumpas av random.randint är ett heltal och värdet som input läser in är en sträng, det ena värdet måste alltså konverteras för att gissning och värde skall kunna jämföras.

Exempel

>>> guess_the_number(1, 5)
Gissa ett tal mellan 1 och 5: 1
Gissa ett tal mellan 1 och 5: 2
Gissa ett tal mellan 1 och 5: 3
Gissa ett tal mellan 1 och 5: 4
Rätt gissat!

Eftersom vi inte vet i förväg hur många gissningar som behövs för att gissa rätt så kan vi inte lösa detta på ett enkelt sätt med for.

1
2
3
4
5
6
7
def guess_the_number(lowest=1, highest=10):
    import random
    secret_number = random.randint(lowest, highest)
    guess = None
    while guess != secret_number:
        guess = int(input(f"Gissa ett tal mellan {lowest} och {highest}: "))
    print("Rätt gissat!")

b)

Nu ska vi göra funktionen lite mer hjälpsam så att vi snabbare ska kunna gissa rätt värde. Lägg till kod i guess_the_number så att funktionen också skriver ut om gissningen är för hög eller för låg.

Exempel

Med strategin vi använde i förra exemplet hade vi behövt i snitt 50 gissningar innan vi gissar rätt. I värsta fall, om det slumpade talet är 100 och vi börjar gissa från 1 och testar värde för värde, så måste vi gissa 100 gånger.

Att funktionen informerar oss om huruvida vår gissing är för hög eller för låg innebär dock att vi kan använda en gissningsstrategi som kallas binärsökning där vi hela tiden halverar hur stort steg vi tar. Vi tar ett exempel där vi ska gissa ett slumpat värde mellan 1 och 100.

>>> guess_the_number(1, 100)
Gissa ett tal mellan 1 och 100: 50
För lågt!
Gissa ett tal mellan 1 och 100: 75
För lågt!
Gissa ett tal mellan 1 och 100: 87
För högt!
Gissa ett tal mellan 1 och 100: 81
För lågt!
Gissa ett tal mellan 1 och 100: 84
För högt!
Gissa ett tal mellan 1 och 100: 82
Rätt gissat!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def guess_the_number(lowest=1, highest=10):
    import random
    secret_number = random.randint(lowest, highest)
    guess = None
    while guess != secret_number:
        guess = int(input(f"Gissa ett tal mellan {lowest} och {highest}: "))
        if guess < secret_number:
            print("För lågt!")
        elif guess > secret_number:
            print("För högt!")
    print("Rätt gissat!")

c)

Vi ska nu vända på förhållandet och låta vårt program gissa istället. Skriv en funktion binary_search_gtn(lowest, highest) som ska använda samma strategi som ovan för att lista ut ett värde som vi har bestämt i förväg. Dvs. istället för att programmet läser in en gissning från oss så gör programmet en gissning och vi svarar ifall gissningen är rätt (r), för låg (l) eller för hög (h).

Exempel

Vi bestämmer att värdet ska vara 23 och informerar programmet att värdet ligger någonstans mellan 1 och 100:

>>> binary_search_gtn(1, 100)
Är talet 50? (r/l/h): h
Är talet 25? (r/l/h): h
Är talet 12? (r/l/h): l
Är talet 18? (r/l/h): l
Är talet 21? (r/l/h): l
Är talet 23? (r/l/h): r
Woho!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def binary_search_gtn(lowest, highest):
    while True:
        guess = (lowest + highest) // 2
        response = input(f"Är talet {guess}? (r/l/h): ")
        if response in ["rätt", "r"]:
            print("Woho!")
            return
        elif response in ["för lågt", "l"]:
            lowest = guess + 1
        elif response in ["för högt", "h"]:
            highest = guess - 1
        else:
            print("Okänt svar, försök igen.")

Tärningsslag

a)

Skriv en funktion dice_rolls_until(n_sides, target_side) som simulerar och lagrar slag av en tärning med n_sides sidor (antag att de är numrerade från 1 och utan uppehåll) till och med att target_side kommer upp.

Exempel

1
2
3
4
>>> dice_rolls_until(6, 6)
[1, 3, 4, 4, 2, 6]
>>> dice_rolls_until(6, 6)
[2, 6]
1
2
3
4
5
6
7
8
def dice_rolls_until(n_sides, target_side):
    import random
    roll = None
    rolls = []
    while roll != target_side:
        roll = random.randint(1, n_sides)
        rolls.append(roll)
    return rolls

b)

Skriv en funktion dice_rolls_until(n_sides, target_sum) som simulerar och lagrar slag av en tärning med n_sides sidor (antag att de är numrerade från 1 och utan uppehåll) tills det att summan av slagen är minst target_sum.

Exempel

1
2
3
4
>>> dice_rolls_until(6, 20)
[6, 5, 5, 1, 2, 4]
>>> dice_rolls_until(20, 100)
[15, 16, 15, 9, 12, 11, 5, 14, 18]
1
2
3
4
5
6
7
8
9
def dice_rolls_until(n_sides, target_sum):
    import random
    rolls = []
    current_sum = 0
    while current_sum < target_sum:
        roll = random.randint(1, n_sides)
        rolls.append(roll)
        current_sum += roll
    return rolls

c)

Skriv en funktion dice_rolls_until(n_sides, target_side, target_sum) som simulerar och lagrar slag av en tärning med n_sides sidor (antag att de är numrerade från 1 och utan uppehåll) tills det att sidan target_side kommer upp eller summan av slagen är minst target_sum.

Exempel

1
2
3
4
>>> dice_rolls_until(20, 100)
[15, 14, 7, 14, 15, 20, 4, 17]
>>> dice_rolls_until(20, 20, 100)
[17, 11, 20]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def dice_rolls_until(n_sides, target_side, target_sum):
    import random
    roll = None
    rolls = []
    current_sum = 0
    while roll != target_side and current_sum < target_sum:
        roll = random.randint(1, n_sides)
        rolls.append(roll)
        current_sum += roll
    return rolls

Muterbarhet och funktionsanrop

Fungerar följande kodsnuttar och i så fall, vad skriver de ut?

a)

1
2
3
4
5
6
7
def add_one(a):
    a = a + 1
    print(a)

x = 5
add_one(x)
print(x)

b)

1
2
3
4
5
6
def append_one(seq):
    seq.append(1)

x = []
append_one(x)
print(x)

c)

1
2
3
4
5
6
def add_to_all(tuple, val):
    for i in range(len(tuple)):
        tuple[i] = tuple[i] + 1

x = (0, 1, 2)
add_to_all(x, 1)

Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2025-09-15