Del A#

För betyg 3 på tentan krävs minst 5/8 godkända uppgifter på del A. För betyg 4 respektive 5 krävs dessutom ett visst antal poäng på del B.

A1#

import math
class Fyrkant:
    def __init__(self, sida):
        pi = math.pi
        self.hojd = 2 * sida
        self.bredd = 3 * sida
        self.area = self.hojd * self.bredd
        self.vinklar = [pi/2, pi/2, pi/2, pi/2]
        self.diag1, self.diag2 = 2 * [math.sqrt(self.hojd ** 2 
                                      + self.bredd ** 2)]
    def parallellogram(self, ang):
        self.vinklar = [ang, pi - ang, ang, pi - ang]
        self.diag1 = math.sqrt(self.hojd ** 2 + self.bredd ** 2 
                               + 2 * self.hojd * self.bredd * math.cos(ang))
        self.diag2 = math.sqrt(self.hojd ** 2 + self.bredd ** 2 
                               - 2 * self.hojd * self.bredd * math.cos(ang))
    def __str__(self):
        return f'Area = {self.area}'

Betrakta koden ovan. Vilket alternativ använder korrekt terminologi för de olika komponenterna i koden?

Alternativ 1:

moduler: math, self
klasser: Fyrkant
funktioner: sqrt, cos
metoder: __init__(), __str__(), diag1, diag2, vinklar
attribut: hojd, bredd, area

Alternativ 2:

moduler: math
klasser: Fyrkant
funktioner: sqrt, cos, area, parallellogram()
metoder: __init__(), parallellogram(), __str__()
attribut: hojd, bredd, area, vinklar, diag1, diag2

Alternativ 3:

moduler: math
klasser: Fyrkant
funktioner: sqrt, cos
metoder: __init__(), parallellogram(), __str__()
attribut: hojd, bredd, area, vinklar, diag1, diag2

Alternativ 4:

moduler: math
klasser: Fyrkant
funktioner: sqrt, cos, parallellogram()
metoder: __init__(), __str__()
attribut: hojd, bredd, area, vinklar, diag1, diag2, self
Svar

Svar: Alternativ 3

A2#

import random as rd
import matplotlib.pyplot as plt
from math import sqrt
from turtle import Turtle

Betrakta koden ovanför. För varje påstående nedanför, ange om det är sant eller falskt. För att få 1 poäng på den här uppgiften krävs 4 av 4 rätta svar.

1. sqrt och Turtle är en funktion respektive en klass från modulerna math respektive turtle. Sant eller falskt?

Svar

Sant

2. sqrt och Turtle är funktioner från modulerna math respektive turtle. Sant eller falskt?

Svar

Falskt

3. rd och plt är funktioner från modulerna random respektive matplotlib.pyplot. Sant eller falskt?

Svar

Falskt

4. rd och plt är import-alias till modulerna random och matplotlib.pyplot. Sant eller falskt?

Svar

Sant

A3#

Skriv en funktion summa_av_slumptal(limit) som summerar en serie slumpmässiga heltal mellan 0 och 10 och returnerar summan av slumptalen. Summan ska returneras när nästa term i serien hade gjort att summan blivit större än en angiven gräns limit.

Förutom summan ska även antalet termer i summan returneras.

Ledning: Importera modulen random med satsen import random. Funktionen random.randint(a, b) ger slumpmässiga heltal mellan a och b.

Exempel: Följande kod

summa, antal = summa_av_slumptal(21)
print(f'Summan av {antal} slumptal blev {summa}.') 

ska till exempel kunna ge utskriften:

Summan av 5 slumptal blev 19.

Utskriften kommer att variera då det är en summa av slumptal.

Lösningsförslag
import random

def summa_av_slumptal(limit):
    previous_sum = 0 # Variable for sum without last random number
    current_sum = 0  # Variable for sum including last random number
    count = 0        # Number of terms in sum
    while current_sum <= limit:
        previous_sum = current_sum
        number = random.randint(0, 10)
        current_sum = previous_sum + number
        count += 1
    count -= 1 # Subtract 1 to get number of terms in previous_sum 
    return previous_sum, count

A4#

Kast med en tärning modelleras med klassen Dice:

import random


class Dice:
    def __init__(self, sides):
        self.sides = sides
        self.num_rolls = 0
        self.sum = 0

    def __str__(self):
        return f'Sidor: {self.sides:2d}, summa: {self.sum:2d} efter {self.num_rolls} kast'

    def roll(self):
        value = random.randint(1, self.sides)
        self.sum += value
        self.num_rolls += 1

Varje tärningsobjekt har ett antal sidor (instansvariabeln sides) och håller koll på hur många gånger tärningen har kastats hittills (instansvariabeln num_rolls) samt summan av alla kast hittills (instansvariabeln sum).

Uppdatera metoden roll så att den tar en parameter number och kastar tärningen number gånger. Metoden ska även uppdatera instansvariablerna num_rolls och sum på lämpligt sätt.

Om man anger ett värde på number som är mindre än 1 skall metoden använda number = 1.

Följande kod

d = Dice(6)
d.roll(5)
print(d)
d.roll(2)
print(d)
d.roll(-2)
print(d)

ska till exempel kunna ge utskriften

Sidor:  6, summa: 17 efter 5 kast
Sidor:  6, summa: 27 efter 7 kast
Sidor:  6, summa: 28 efter 8 kast

Notera att du endast får uppdatera metoden roll i klassen. Du får INTE göra några andra ändringar i koden.

Lösningsförslag
def roll(self, number):
    if number < 1:
        number = 1
    for _ in range(number):
        value = random.randint(1, self.sides)
        self.sum += value
        self.num_rolls += 1

A5#

Skriv en funktion word_count(text) som tar en sträng text som parameter. Funktionen ska ta reda på vilka ord som förekommer i texten samt hur många gånger varje ord förekommer. Denna information ska returneras i form av en lista där varje element är en tupel med ordet som första element och antal förekomster som andra element. Listan ska vara sorterad i bokstavsordning och orden ska vara skrivna med enbart gemener (små bokstäver).

Ledning 1: Använd ett lexikon.

Ledning 2: Du kan plocka ut en lista med alla ord i en text med hjälp av följande kod

import re
list_of_words = re.findall(r'[a-zA-ZåäöÅÄÖ]+', text) 

Körexempel: Följande kod

text = 'O jerum, jerum, jerum, O quae mutatio rerum'
word_list = word_count(text)
for element in word_list:
    print(f'{element[0]}:{element[1]}',end = ', ')

ska resultera i utskriften

jerum:3, mutatio:1, o:2, quae:1, rerum:1,
Lösningsförslag
import re

def word_count(text):
    list_of_words = re.findall(r'[a-zA-ZåäöÅÄÖ]+', text)
    word2count = {} # Dict of words and number of occurrences
    for word in list_of_words:
        word = word.lower()
        if word in word2count:
            word2count[word] += 1
        else:
            word2count[word] = 1
    return (sorted(word2count.items()))    

A6#

En text är skriven på ett tangentbord som saknar de svenska bokstäverna å, ä, ö, Å, Ä samt Ö. Istället har man använt följande ”översättningstabell”:

  • { för å

  • } för ä

  • | för ö

  • [ för Å

  • ] för Ä

  • _ för Ö

Skriv en funktion svenska_tecken(text) som tar en sträng text som parameter och returnerar en motsvarande sträng där {, }, |, [, ] och _ har ersatts med å, ä, ö, Å, Ä och Ö.

Exempel (från en dikt av Gustav Fröding)

Följande kod

text = """D’} e {, vett ja”, skrek ja, f|r ja ble rasen, ”{ i {a } e |, h|rer han lite, d’} e {, { i {a } e |."""
print(svenska_tecken(text))

ska resultera i utskriften

D’ä e å, vett ja”, skrek ja, för ja ble rasen, ”å i åa ä e ö, hörer han lite, d’ä e å, å i åa ä e ö.
Lösningsförslag 1
def svenska_tecken(text):
    text = text.replace('{','å')
    text = text.replace('}','ä')
    text = text.replace('|','ö')
    text = text.replace('[','Å')
    text = text.replace(']','Ä')
    text = text.replace('_','Ö')
    return text
Lösningsförslag 2
def svenska_tecken(text):
    text_ut = text
    translation_dict = {'{': 'å', '}': 'ä', '|': 'ö', '[': 'Å', ']': 'Ä', '_': 'Ö'}
    for old, new in translation_dict.items():
        text_ut = text_ut.replace(old, new)
    return text_ut

A7#

Skriv en funktion statistik(lst) som beräknar medelvärde och median för en lista lst med n tal. Funktionen behöver kunna hantera listor med både jämna och udda antal värden.

OBS! Du får inte använda modulen statistics.

Ledning 1: För beräkning av medianen är det smidigt om listan är sorterad i storleksordning. Medianen är då det mittersta värdet i fallet när n är udda. Om n är jämnt är medianen medelvärdet av de två mittersta värdena.

Körexempel: Koden

lst = [10, 1, 3, 4, 2]
medelvarde, median = statistik(lst)
print(f'Medianen är {median} och medelvärdet är {medelvarde}.')

ska resultera i utskriften

Medianen är 3 och medelvärdet är 4.0.
Lösningsförslag
def statistik(lst):
    lst = sorted(lst)
    n = len(lst)
    medelvarde = sum(lst)/n
    if n%2 != 0:
        median = lst[n//2]
    else:
        median = (lst[n//2 - 1] + lst[n//2])/2
    return medelvarde, median

A8#

Du har gjort ett experiment tillsammans med en kursare där ni, var för sig, varje hel timme mätt temperaturen T i ett prov. Ni har sparat mätningarna i varsitt lexikon med klockslaget som nyckel och temperaturen (i grader Celsius) som värde. I labbrapporten ska ni redovisa medelvärdena av era temperaturmätningar. Det visar sig dock att ni sparat data på två olika sätt.

  • Du har sparat lexikonet data_1 = {'12':1.1,'13':2.8,'14':3.4}

  • Din kursare har sparat lexikonet data_2 = {'14:00':'3.5','13:00':'2.9','12:00':'1.4'}

Skriv en funktion merge_data(data_1, data_2) som tar de två olika lexikonen som parametrar och returnerar ett lexikon med klockslagen som nycklar och medelvärdena av era mätningar som värden. Funktionen ska fungera även för andra lexikon med samma format (men exempelvis fler mätpunkter).

Ledning: Du kan utgå från att data läses in i ordningen data_1, data_2. Du kan också anta att de båda lexikonen innehåller samma klockslag (hela timmar), bara skrivna med eller utan :00.

Körexempel: Koden

data_1 = {'12': 1.1, '13': 2.8, '14': 3.4}
data_2 = {'14:00': '3.5', '13:00': '2.9', '12:00': '1.4'}
merged_data = merge_data(data_1, data_2)
for time in merged_data:
    print(f' Kl {time} var temperaturen {merged_data[time]:.2f} grader Celsius.')

ska ge utskriften

Kl 12 var temperaturen 1.25 grader Celsius.
Kl 13 var temperaturen 2.85 grader Celsius.
Kl 14 var temperaturen 3.45 grader Celsius.
Lösningsförslag
def merge_data(data_1, data_2):
    merged_data = {}
    for time_1 in data_1:
        temp_1 = data_1[time_1]
        time_2 = time_1 + ':00'
        temp_2 = float(data_2[time_2])

        merged_time = time_1
        merged_temp = (temp_1 + temp_2) / 2
        merged_data[merged_time] = merged_temp
    return merged_data

Del B#

För betyg 4 resp. 5 krävs godkänd del A samt 8 resp. 12 poäng av 16 möjliga på del B.

På midsommarafton på landet brukar somliga arrangera ett ”potatisrace”. Racet går till så att man placerar en potatis på en sked, sätter skeden i munnen, och sedan försöker gå en sträcka och balansera potatisen. Följande regler gäller:

  • Först i mål vinner.

  • Tappar man potatisen måste man gå tillbaka till start och börja om från början.

  • Om två personer kommer i mål samtidigt vinner den som är yngst.

Din uppgift är att simulera ett potatisrace. Till din hjälp har du klassen Participant, vars konstruktor är given nedan. De tre instansvariablerna har följande betydelser:

  • age: heltal, deltagarens ålder

  • position: heltal, deltagarens position på banan. 0 motsvarar startpositionen och length - 1 motsvarar mållinjen på en bana av längd length.

  • direction: deltagarens nuvarande riktning, antingen 1 (framåt) eller -1 (bakåt).

class Participant:
    def __init__(self, age, position=0, direction=1):
        self.age = age
        self.position = position
        self.direction = direction

B1 (2 poäng)#

Skapa en klass Potatisrace. Skriv en konstruktor __init__(self, length, num_participants) som tar parametrarna length (heltal, längden på banan) och num_participants (heltal, antal deltagare).

Klassen Potatisrace ska ha följande två instansvariabler:

  • length: banans längd

  • participants: en lista med num_participants element. Dessa ska vara objekt av klassen Participant och representerar deltagarna i racet. Initialt ska alla deltagare stå på startlinjen och titta framåt. Deltagarnas åldrar ska vara slumpmässiga heltal mellan 10 och 80.

Ledning: Importera modulen random och använd funktionen random.randint(min, max) för att slumpa fram åldrarna.

Lösningsförslag
import random 


class Potatisrace:
    def __init__(self, length, num_participants):
        self.length = length
        self.participants = []
        position = 0
        direction = 1
        for _ in range(num_participants):
            age = random.randint(10, 80)
            self.participants.append(Participant(age, position, direction))

B2 (3 poäng)#

I klassen Potatisrace, lägg till en __str__()-metod som illustrerar loppet vid ett givet ögonblick. Positionen för varje deltagare markeras med bokstaven P. Nedan visas hur utskriften ska se ut för ett exempel med tre deltagare och banlängd 5.

Initialt, när alla deltagare står på startlinjen ska utskriften vara:

[P    ]
[P    ]
[P    ]

Senare in i loppet, då deltagarnas positioner ändrats, ska utskriften till exempel kunna bli:

[ P   ]
[    P]
[   P ]

Här ser vi att deltagaren på bana 2 precis gått i mål och vunnit racet.

Lösningsförslag
    def __str__(self) :
        res = '\n'
        for p in self.participants:
            # Create a list of n blank spaces
            list_of_chars = [' ']*self.length

            # Put a 'P' where the particiant is
            list_of_chars[p.position] = 'P'

            row_str = ''.join(list_of_chars)
            res += '[' + row_str + ']\n'
        return res

B3 (3 poäng)#

Lägg till en metod step() i klassen Participant. Metoden ska ta en parameter drop_prob som motsvarar sannolikheten att deltagaren tappar potatisen i samband med ett steg (drop_prob=0.2 motsvarar 20% risk att tappa potatisen, t.ex.). Metoden ska, i tur och ordning, utföra följande:

  1. Gå ett steg (av längdenhet 1) i deltagarens nuvarande riktning, dvs. antingen ett steg framåt eller ett steg bakåt.

  2. Generera ett slumptal som avgör huruvida deltagaren tappar sin potatis i samband med detta steg. Om potatisen tappas ska deltagaren vända sig bakåt, dvs. riktningen ska sättas till -1.

  3. Om deltagaren nu står på startlinjen, sätt dess riktning till 1.

Ledning: Anropet random.random() returnerar ett slumpmässigt decimaltal mellan 0 och 1.

Lösningsförslag
    def step(self, drop_prob):
        """Complete one step. drop_prob is the probability of dropping the potato"""

        # Take a step in the current direction
        self.position += self.direction

        # If the potato was dropped, set direction to -1
        r = random.random()
        if r < drop_prob:
            self.direction = -1
        
        # If the participant is at the starting position, set direction to 1
        # (regardless of whether the potato was dropped in this step)
        if self.position == 0:
            self.direction = 1

B4 (2 poäng)#

Lägg till en metod step_all() i klassen Potatisrace som tar ett steg med alla deltagare. Metoden ska ta en parameter drop_prob som används för alla deltagare.

Lösningsförslag
    def step_all(self, drop_prob=0.2):
        """Step all race participants"""
        for p in self.participants:
            p.step(drop_prob)

B5 (2 poäng)#

I klassen Potatisrace, skriv en metod is_finished som returnerar True om minst en deltagare nått målet, annars False. Om en deltagare står på position length - 1, där length är banans längd, räknas det som att hen nått målet.

Lösningsförslag
    def is_finished(self):
        """Return True if at least one participant reached the finish line, otherwise False"""
        for p in self.participants:
            if p.position == self.length-1:
                return True
        return False

B6 (4 poäng)#

I klassen Potatisrace, lägg till och färdigställ metoden determine_winner():

    def determine_winner(self):
        """Return the index of the winning participant. If multiple participants have reached
        the finish line, the youngest one wins. This method assumes that there is at least one 
        winner so that is_finished() returns True."""

Metoden ska returnera det index i listan participants som svarar mot den vinnande deltagaren. Kom ihåg att om flera deltagare nått målet i samma steg så vinner den yngsta!

När du löst denna uppgift ska du kunna simulera ett potatisrace med nedanstående main-funktion.

def main():
    drop_prob = 0.2
    race = Potatisrace(5, 3)
    print(race)
    while not race.is_finished():
        race.step_all(drop_prob)
        print(race)
    winning_index = race.determine_winner()
    winning_age = race.participants[winning_index].age
    print(' ')
    print(f'Racet klart! Vinnare är bana {winning_index+1} med en deltagare som är {winning_age} år gammal')

if __name__ == '__main__':
    main()

Den avslutande utskriften ska till exempel kunna bli som nedan.

Racet klart! Vinnare är bana 1 med en deltagare som är 47 år gammal

[         P ]
[         P ]
[ P         ]
Lösningsförslag
    def determine_winner(self):
        """Return the index of the winning participant. If multiple particpants have reached
        the finish line, the youngest one wins. This method assumes that there is at least one 
        winner so that is_finished() returns True."""
        winners_age = 1000 # Set winning age to some large number
        for index, p in enumerate(self.participants):
             if p.position == self.length - 1 and p.age < winners_age:
                 winners_age = p.age
                 winners_index = index 

        return winners_index
Lösningsförslag B1-B6
import random 


class Participant:
    def __init__(self, age, position=0, direction=1):
        self.age = age
        self.position = position
        self.direction = direction

    def step(self, drop_prob):
        """Complete one step. drop_prob is the probability of dropping the potato"""

        # Take a step in the current direction
        self.position += self.direction

        # If the potato was dropped, set direction to -1
        r = random.random()
        if r < drop_prob:
            self.direction = -1
        
        # If the participant is at the starting position, set direction to 1
        # (regardless of whether the potato was dropped in this step)
        if self.position == 0:
            self.direction = 1


class Potatisrace:
    def __init__(self, length, num_participants):
        self.length = length
        self.participants = []
        position = 0
        direction = 1
        for _ in range(num_participants):
            age = random.randint(10, 80)
            self.participants.append(Participant(age, position, direction))
    
    def __str__(self) :
        res = '\n'
        for p in self.participants:
            # Create a list of n blank spaces
            list_of_chars = [' ']*self.length

            # Put a 'P' where the particiant is
            list_of_chars[p.position] = 'P'

            row_str = ''.join(list_of_chars)
            res += '[' + row_str + ']\n'
        return res

    def step_all(self, drop_prob=0.2):
          """Step all race participants"""
          for p in self.participants:
            p.step(drop_prob)  

    def is_finished(self):
        """Return True if at least one participant reached the finish line, otherwise False"""
        for p in self.participants:
            if p.position == self.length-1:
                return True
        return False
    
    def determine_winner(self):
        """Return the index of the winning participant. If multiple participants have reached
        the finish line, the youngest one wins. This method assumes that there is at least one 
        winner so that is_finished() returns True."""
        winners_age = 1000 # Set winning age to some large number
        for index, p in enumerate(self.participants):
             if p.position == self.length - 1 and p.age < winners_age:
                 winners_age = p.age
                 winners_index = index 

        return winners_index