Demotenta HT23#

Information#

Tentamen består av två delar, del A och del B.

  • För betyg 3 krävs 5/8 på del A.

  • För betyg 4 krävs betyg 3 samt 5/12 på del B.

  • För betyg 5 krävs betyg 3 samt 9/12 på del B.

Del B rättas enbart om del A är godkänd.

Hjälpmedel#

Via den dator du skriver tentamen på har du åtkomst till:

Länkar till dessa hjälpmedel finns som flikar längst ned i Inspera.

Eftersom du har tillgång till en programmeringsmiljö kan du testa din kod innan du lämnar in den i Inspera. Du kopierar ditt svar från Programiz/Online Python/GDB Online till Inspera. Markera koden med CTRL-A, kopiera med CTRL-C och klistra in i svarsrutan i Inspera med CTRL-V. Ångra med CTRL-Z.

Medan du skriver tentamen sparas dina svar i Inspera var femtonde sekund. Du kan inte öppna tentan igen när du har lämnat in den.

Bedömning#

I del A krävs genomgående att din kod ska fungera enligt anvisning. Kod som inte fungerar ges endast i undantagsfall något annat än 0 poäng.

I del B kan du få delpoäng för icke-fungerande kod som ändå visar att du kommit en bit på vägen.

Kommentering av kod behövs ej för del A. Uppgifterna på del B bör kommenteras på de ställen där en kommentar ökar läsbarheten markant. Svenska kommentarer är ok.

Såvida inget annat anges får du bygga på lösningar till föregående uppgifter även om du inte har löst dessa. Det är tillåtet att införa extra metoder eller funktioner. Uttryck som ”skriv en funktion som” ska alltså inte tolkas så att lösningen inte får struktureras med hjälp av fler funktioner. Alla uppgifter gäller programmeringsspråket Python och all programkod ska skrivas i Python. Koden ska vara läslig, dvs. den ska vara vettigt strukturerad och indenterad. Namn på variabler, funktioner, metoder, klasser etc. ska vara beskrivande men kan ändå hållas ganska korta.

A1#

Nedan är innehållet i en funktion. Den första raden (”funktionshuvudet”) i funktionsdefinitionen har dock försvunnit. Hur bör den se ut?

    index = 0
    max_value = lst[0]
    for i in range(len(lst)):
        if lst[i] > max_value:
            max_value = lst[i]
            index = i
    return max_value, index

Alternativ 1: def max_value_with_index():

Alternativ 2: def max_value_with_index(lst):

Alternativ 3: def max_value_with_index(lst, i):

Alternativ 4: def shuffle_list():

Alternativ 5: def shuffle_list(lst):

Alternativ 6: def shuffle_list(lst, i):

Svar

Alternativ 2.

A2#

Hur många olika listor skapar följande kod?

a = [1, [2, 3]] 
b = a
c = a[1]
d = a[1][0]
e = a[1][1]
print(a.pop)

a) 1, b) 2, c) 3, d) 4, e) 5

Svar

Bara 2 st listobjekt skapas (alternativ b)). Dessa skapas på den första raden. Variablerna b och c är bara referenser till tidigare skapade listor. pop är inget anrop, det är en referens till den metoden (eftersom parenteser saknas).

A3#

Skriv en funktion equal_elements_at_same_index som tar två listor med heltal som parametrar. Funktionen ska returnera antal element som är lika och på samma position i de två listorna. Du kan anta att listorna är lika långa.

Följande anrop:

x = [1, 2, 3, 4, 5, 6, -1]
y = [1, 7, 3, 4, 2, 2, -1]
print(equal_elements_at_same_index(x, y))

ska ge utskriften

4

I detta exempel är det siffrorna 1, 3, 4 och -1 som är lika och på samma position. Siffran 2 räknas exempelvis inte, eftersom den inte finns på samma position i de två listorna.

Lösningsförslag
def equal_elements_at_same_index(lst1, lst2):
    count = 0
    for i in range(len(lst1)):
        if lst1[i] == lst2[i]:
            count += 1
    return count

A4#

Inför en tenta skapas ett lexikon name_to_code som har studenternas namn som nycklar och deras tentakoder som värden. När tentan sedan rättas ser läraren inte studenternas namn utan bara tentakoderna. Läraren skapar då ett lexikon code_to_grade som har tentakoderna som nycklar och motsvarande betyg som värden. I körexemplet nedan visas hur dessa lexikon kan se ut.

Din uppgift är att skriva en funktion build_name_to_grade(name_to_code, code_to_grade) som utifrån ovan beskrivna lexikon konstruerar och returnerar ett nytt lexikon där studenternas namn är nycklar och betygen de fick på tentan är värden.

Följande kod:

name_to_code = {'Adam Andersson': '1461A-0001-XJF',
                'Berit Bengtsson': '1461A-0002-QWR',
                'Casper Carlsson': '1461A-0003-CET'}

code_to_grade = {'1461A-0001-XJF': '3',
                 '1461A-0002-QWR': '4', 
                 '1461A-0003-CET': '5'}

name_to_grade = build_name_to_grade(name_to_code, code_to_grade)
for name in name_to_grade:
    grade = name_to_grade[name]
    print(f'{name} got grade {grade}.')

ska ge utskriften

Adam Andersson got grade 3.
Berit Bengtsson got grade 4.
Casper Carlsson got grade 5.

Din funktion ska fungera för generella lexikon med den struktur som beskrivs ovan.

Lösningsförslag
def build_name_to_grade(name_to_code, code_to_grade):
    name_to_grade = {}
    for name in name_to_code:
        code = name_to_code[name]
        grade = code_to_grade[code]
        name_to_grade[name] = grade
    return name_to_grade

A5#

Skriv en funktion rolls_required() som simulerar att en tärning kastas tills dess att alla tärningens sidor visats minst en gång. Funktionen ska returnera antalet tärningskast som krävdes.

Notera att funktionen inte ska ta några parametrar.

Tips: Anropet random.randint(1, 6) returnerar ett slumpmässigt heltal mellan 1 och 6 som du kan använda för att simulera ett tärningskast.

Utfallet ska vara slumpmässigt, men följande kod:

num_rolls = rolls_required()
print(f'It took {num_rolls} rolls for all sides of the die to appear at least once.')

Ska till exempel kunna ge utskriften

It took 10 rolls for all sides of the die to appear at least once.
Lösningsförslag
import random

def rolls_required():
    count = 0
    has_appeared = [False]*6
    while False in has_appeared:
        roll = random.randint(1, 6)
        has_appeared[roll-1] = True
        count += 1
    return count

A6#

Definiera en ny klass Point som beskriver en koordinat i ett tvådimensionellt rum. Dess konstruktor ska ta två parametrar x och y och internt lagra koordinaterna som en tupel i en instansvariabel pos. Det ska inte finnas några andra instansvariabler.

Lösningsförslag
class Point:
    def __init__(self, x, y):
        self.pos = (x, y)

A7#

Skriv en ny metod rot90 i klassen Point. Metoden ska rotera den befintliga punkten i steg om 90 grader moturs. p.rot90() ska vrida 90 grader moturs, p.rot90(2) ska vrida 180 grader moturs, p.rot90(5) ska vrida 90 grader moturs, osv.

Ledning: Om punkten \((x, y)\) roteras 90 grader moturs kan de nya roterade koordinaterna \((x_r, y_r)\) skrivas som:

\(x_r = −y, \quad y_r = x\)

Lösningsförslag
    def rot90(self, n=1):
        for _ in range(n % 4):
            self.pos = (-self.pos[1], self.pos[0])

A8#

Skriv en funktion reversed_name som tar en sträng som motsvarar ett namn som parameter. Funktionen ska returnera en sträng med namnet skrivet baklänges. Det bakvända namnet ska börja med versal (stor bokstav) och därefter enbart innehålla gemener (små bokstäver).

Följande kod:

print(reversed_name('Michael'))
print(reversed_name('m'))

ska ge utskriften

Leahcim
M
Lösningsförslag
def reversed_name(name):
    rev_name = name[-1].upper() + name[-2::-1].lower()
    return rev_name

B1 (4p)#

Skriv en funktion analyze_alliterations(sentence) som bestämmer antalet allitterationer i en mening. Här definieras en allitteration som en obruten följd av ord som börjar på samma bokstav. Funktionen ska returnera en tupel med två element: antal allitterationer i texten och antalet ord i den längsta allitterationen.

Tips: Anropet re.findall(r'[a-zA-ZåäöÅÄÖ]+', sentence) returnerar en lista med alla ord i strängen sentence.

Följande kod:

sent1 = "From a cheap and chippy chopper on a big black block"
print(analyze_alliterations(sent1))

sent2 = """To sit in solemn silence in a dull dark dock
in a pestilential prison with a lifelong lock
awaiting the sensation of a short sharp shock"""
print(analyze_alliterations(sent2))

ska ge utskriften

(2, 3)
(5, 3)
Lösningsförslag
import re

def analyze_alliterations(sentence):
    words = re.findall(r'[a-zA-ZåäöÅÄÖ]+', sentence)
    num_allit = 0
    longest_len = 0
    current_len = 0

    allit_char = words[0][0].lower()
    for word in words[1:]:
        first_char = word[0].lower()

        if first_char == allit_char and current_len == 0:
            # We found a new alliteration!
            current_len = 2
            num_allit += 1
        elif first_char == allit_char:
            # This  is an ongoing alliteration
            current_len += 1
        elif current_len > 0 and first_char != allit_char:
            # An alliteration just came to an end
            longest_len = max(longest_len, current_len)
            current_len = 0
            allit_char = first_char
        else:
            # This is not an alliteration
            allit_char = first_char

    # The sentence might end with an alliteration, which might be the longest
    longest_len = max(longest_len, current_len)
    return num_allit, longest_len

B2 (8p)#

Denna uppgift handlar om en klass som simulerar spelet Yatzy. Det spelas med fem st sexsidiga tärningar. Här bryr vi oss bara om att få ”YATZY”, det vill säga att alla tärningar visar samma värde. Vi har tre kast på oss att åstadkomma detta. I det första kastet måste vi kasta alla tärningar. I kast två och tre får vi välja vilka tärningar vi vill kasta om. För att maximera chansen för YATZY sparar vi alla tärningar med det värde som vi har flest av. Om vi har lika många av två eller flera värden så sparar vi det högsta värdet (det påverkar ändå inte sannolikheten att få YATZY).

Nedan är klassen Dice färdigskriven och klassen Yatzy påbörjad.

Din uppgift är att skriva klart nedanstående fyra metoder i klassen Yatzy enligt beskrivningarna.

  • roll_all_except(self, value_to_keep) ska kasta alla tärningar utom de som redan visar värdet value_to_keep.

  • most_common_value(self) ska returnera det värde som flest tärningar visar. Om flera värden är lika vanliga ska det högsta värdet returneras.

  • all_equal(self) ska returnera True om vi just nu har YATZY, annars False.

  • roll_for_yatzy(self) ska simulera tre kast där vi gör vårt bästa för att få YATZY.

Du får föra in egna hjälpmetoder eller hjälpfunktioner om du vill.

När du är klar kan du använda följande kod för att uppskatta sannolikheten att få YATZY:

yatzy_obj = Yatzy()
count = 0
num_attempts = 1000
for _ in range(num_attempts):
    yatzy_obj.roll_for_yatzy()
    if yatzy_obj.all_equal():
        count += 1
print(f'Estimated probability for Yatzy: {count/num_attempts}.')
import random

class Dice:
    def __init__(self, sides):
        self.sides = sides
        self.value = random.randint(1, self.sides)

    def __str__(self):
        return f'Sides: {self.sides:2d}, value: {self.value:2d}'

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

class Yatzy:
    def __init__(self):
        self.dice = [Dice(6) for _ in range(5)]

    def __str__(self):
        res = ''
        for d in self.dice:
            res += f'{d.value} '
        return res
    
    def roll_all(self):
        for d in self.dice:
            d.roll()   

    def roll_all_except(self, value_to_keep):
        pass

    def most_common_value(self):
        pass
    
    def all_equal(self):
        pass
    
    def roll_for_yatzy(self):
        pass
Lösningsförslag
    def roll_all_except(self, value_to_keep):
        for d in self.dice:
            if d.value != value_to_keep:
                d.roll()

    def most_common_value(self):
        max_count = 0
        values = [d.value for d in self.dice]
        for value in range(1, 6+1):
            count = values.count(value)
            if count >= max_count:
                max_count = count
                most_common_value = value
        return most_common_value
    
    def all_equal(self):
        value0 = self.dice[0].value
        for d in self.dice[1:]:
            if d.value != value0:
                return False
        return True
    
    def roll_for_yatzy(self):
        # Roll 1
        self.roll_all()

        # Rolls 2-3
        for _ in range(2):
            value_to_keep = self.most_common_value()
            self.roll_all_except(value_to_keep)