EDAA70/80

Programmeringsteknik Python — Tentaguide

↑ Alla kurser

Klara tentan på en dag 🐍

Det här är inte en lista med facit — det är ett recept. Specen talar om exakt vad du ska skriva. Lär dig översätta "vad specen säger" → "vad du skriver", så löser du uppgifter du aldrig sett förut.

Läs specen som ett recept Samma steg varje gång Förstå varför
📄
Du får ha Python-snabbreferensen (LU) på tentan. Lär dig inte syntax utantill — slå upp den. Memorera istället mönstren i den här guiden: vilken metod/datastruktur du behöver. Snabbreferensen säger hur du skriver d.get, sorted, s.split(), set-operationer m.m.; guiden säger när.

1Tentans upplägg — alltid samma form

Varje extenta har samma skelett, bara med nya ord (rätter, multiplikationer, bokningar…). Känner du igen formen vet du redan ungefär vad varje uppgift vill ha:

UPPGIFT 1
Enkel klass
Ett "ting" med några egenskaper. Bara __init__ + ev. __str__. Används av resten.
UPPGIFT 2 · tyngst
Containerklass
Håller många av tinget från uppg. 1. Lägg till, räkna, filtrera, kolla status.
UPPGIFT 3
Hjälpfunktion
Fristående funktion, ingen klass. Bygger eller räknar ut något och returnerar.
UPPGIFT 4
Huvudprogram
Loop + input() som binder ihop allt. Texterna måste matcha exakt.
UPPGIFT 5
Dict / Set
Helt fristående. Nyckel-värdetabeller eller mängder.
Ordning: lös uppifrån. Senare metoder bygger på tidigare, och testfallen körs i samma ordning — en trasig tidig metod ger följdfel. Fastnar du? Skriv pass, gå vidare, kom tillbaka.

2Grundprincipen: specen = receptet

Du behöver aldrig "komma på" en lösning. Specen säger redan allt — du översätter bara svenska till kod. Två saker i specen avgör nästan allt: returtypen (pilen ->) och verbet i beskrivningen.

🧭
Returtypen talar om vad metoden ska lämna tillbaka
Titta alltid på -> ??? först
-> int + ordet "antal"return len(...) eller en räknare
-> bool + "om / kontrollera"return <villkor> (ett True/False-uttryck)
-> None + "uppdaterar / lägger till"ändra self, ingen return
-> list + "alla som …"bygg lista i en loop, return den
-> str (ofta __str__)bygg en f-sträng i exakt rätt format
-> KlassNamnreturn KlassNamn(...) — skapa ett nytt objekt
💡
Varför funkar det här? Hela kursen handlar om att dela upp ansvar. Varje metod har EN tydlig uppgift, och returtypen är kontraktet: "ge mig X så ger jag dig Y". När du läser -> bool vet du att svaret är ja/nej — du behöver inte skriva ut något, inte ändra något, bara return ett villkor.
🔑
Verbet talar om hur
Ord i beskrivningen → kodmönster
"för varje …"for x in ...:
"unika / inga dubbletter"använd set
"koppla X till Y"använd dict  {x: y}
"i samma ordning som"använd list
"sorterad"sorted(...)
"slumpmässig"import random + random.choice / shuffle
"den med högst/flest"max(d, key=d.get)

3Egenskaper — klassens publika kontrakt

Står det Egenskaper: högst upp i en klasspec är det en checklista över attribut klassen MÅSTE ha — inget du väljer själv. Testfallen läser dem direkt med objekt.namn, så de ska vara publika (inget understreck) och heta exakt som specen säger.

Varje rad ger dig tre saker

Ta exemplet från en tenta:

Egenskaper:
  attempts: int (publikt åtkomlig)
      Antalet försök. Från början är antalet försök 0.
  solved_time: int (publikt åtkomlig)
      Tidpunkten i minuter när uppgiften är löst. Från början är
      uppgiften olöst och värdet är därför -1.

Läs varje rad i tre delar — namn, typ och startvärde (som ofta gömmer sig i beskrivningen):

Namnetattemptsself.attempts — exakt så, publikt
Typeninttalar om vad slags värde (här ett heltal)
"Från början … 0"startvärde: self.attempts = 0
"olöst … därför -1"startvärde: self.solved_time = -1
→ Det blir din konstruktor
def __init__(self):
    self.attempts = 0       # publikt, startvärde ur texten
    self.solved_time = -1   # -1 betyder "ännu olöst"

Lägg märke till: ingen av dem skickas in som parameter — de börjar på ett bestämt värde. Startvärdet står nästan aldrig som en siffra i listan, utan i den löpande texten: "från början…", "initialt…", "därför…".

💡
Varför exakt namn och publikt? En egenskap är klassens löfte utåt: "du kan läsa obj.attempts när som helst". Testfallen förlitar sig på det. Stavar du fel (self.attempt) eller gör den privat (self._attempts) hittar testet den inte → 0p, även om logiken är rätt. Att (publikt åtkomlig) står utskrivet är en extra påminnelse om just detta.
Tumregel: står attributet under Egenskaper: → publikt med exakt namn. Allt annat du själv inför för att lösa uppgiften (räknare, hjälp-listor) → privat. Mer om den gränsen i avsnitt 8.

4Konstruktorn — alltid samma start

Nästan varje klass börjar likadant. Gå igenom de här stegen i ordning, varje gång:

STEG 1
Allt som står som parameter → spara på self

Står parametern i konstruktorns signatur? Då sparar du den rakt av:

def __init__(self, namn, pris):
    self.namn = namn      # self.X = X — samma namn, alltid
    self.pris = pris
💡
Varför self.x = x? Parametern x försvinner när konstruktorn är klar. self.x lever kvar i objektet så att andra metoder kan läsa det senare. self = "det här objektet".
STEG 2
Nämner specen en egenskap som inte är en parameter? → initiera tom

Om beskrivningen pratar om något objektet ska "hålla reda på" som inte skickas in, skapar du det tomt. Vilket tomt värde? Det beror på vad det ska göra:

"en lista av …" / "alla …"self.x = []
"koppla / nyckel-värde"self.x = {}
"en mängd / unika"self.x = set()
"antal / räknare"self.x = 0
"har/är (en status)"self.x = False
# Exempel: "håller reda på alla ordrar" + "lediga platser"
def __init__(self, total_seats):
    self.total_seats = total_seats   # STEG 1: parameter
    self.free_seats = total_seats    # härledd från parameter
    self.bookings = set()           # STEG 2: tom, "unika bokningar"
STEG 3
Behöver du spåra status per element? → parallell struktur

Om du måste minnas något om varje element (t.ex. "är den här frågan rätt besvarad?"), skapa en lista lika lång som datan:

def __init__(self, questions):
    self.questions = questions          # datan
    self._correct = [False] * len(questions)  # privat: egen statusflagga
💡
Varför parallell lista? Index i i båda listorna hör ihop: questions[i] har statusen _correct[i]. Enkelt och räcker långt på tentan.
⚠️
Statusflaggan står inte under "Egenskaper:" i specen — den är din egen → privat (self._correct). Se avsnitt 8.
⚠️
Tar konstruktorn emot en lista? Skriv self.items = itemsinte self.items = []. Du ska spara det du fått, inte slänga det.

5Vilken datastruktur? (lista / dict / set)

Fel datastruktur gör uppgiften svår; rätt gör den nästan trivial. Specens ord avgör — du behöver aldrig gissa:

"ordning spelar roll" · "samma ordning som"list []
"koppla A till B" · "från … till …" · "nyckel-värde"dict {}
"unika" · "inga dubbletter" · "snabb uppslagning"set set()
"två värden som hör ihop" (namn+antal)tuple (a, b)
💡
Varför bryr sig tentan om set? Att kolla x in lista går igenom alla element (långsamt); x in mängd är direkt (snabbt). Därför står det ibland "för full poäng får inga loopar förekomma" — det är en vink om att använda set/dict istället för att loopa.
Vanligt: dict och lista samtidigt

"Koppla bordsnummer till order, men spara också alla ordrar under dagen" → båda behövs:

self._orders = {}      # bordsnr → senaste order (kan skrivas över)
self._all_orders = []  # historik: varje order någonsin

När du lägger till uppdaterar du båda:

def add_order(self, table, order):
    self._orders[table] = order   # skriver över om bordet fanns
    self._all_orders.append(order) # läggs alltid till i historiken

6Metoder — recept efter vad de returnerar

Nu kopplar vi ihop allt. Slå upp returtypen i avsnitt 2, välj receptet nedan, fyll i orden från just din uppgift.

→ INT (räkna)
"antal X"
# Antal element → len
return len(self.items)

# Räkna de som uppfyller villkor → räknare i loop
n = 0
for x in self.items:
    if <villkor>:
        n += 1
return n
→ BOOL (ja/nej)
"kontrollera om …", "returnerar True om …"

Returnera villkoret direkt — skriv aldrig if villkor: return True else: return False.

# Finns elementet?
return (name, num) in self.bookings

# Gäller det för ALLA? → börja True, sätt False vid motbevis
for c in self._correct:
    if not c:
        return False
return True          # kortform: return all(self._correct)
💡
Varför "börja True"? "Alla är rätt" är sant tills du hittar ett undantag. Hittar du ett fel → returnera False direkt. Kommer du igenom loopen utan motbevis → True.
→ NONE (ändra tillstånd)
"lägger till …", "uppdaterar …"

Ingen return. Du ändrar self (eller en inskickad dict — "in-place").

def add_dish(self, name, price, is_vegan):
    self.dishes.append(Dish(name, price, is_vegan))
⚠️
"in-place" betyder ändra objektet du fick, inte skapa ett nytt. data[prod].append(...) ändrar in-place; data = {...} skapar en ny (fel).
→ LIST (filtrera/samla)
"alla X som …", "en lista med …"
result = []
for x in self.items:
    if <villkor>:
        result.append(x)
return result

Behöver du behålla ursprunglig ordning? Loopa över range(len(...)) så du har index till parallella listor:

for i in range(len(self.items)):
    if not self._correct[i]:
        result.append(self.items[i])
→ STR (__str__)
Bygg exakt det format specen visar
def __str__(self):
    if not self.dishes:               # specialfall först
        return "Inga rätter beställda."
    text = ""
    for d in self.dishes:
        text += str(d) + "\n"    # en rad per rätt
    text += f"\nTotalt: {self.total_price()} kr"
    return text
🚨
Formatet måste matcha tecken för tecken. Mellanslag, parenteser, "kr", radbrytningar — kopiera från exemplet i uppgiften. Testet jämför exakt; tryck "Show Differences" i Canvas.
→ KLASS (skapa nytt objekt)
"returnerar ett nytt X med …"

Återanvänd dina egna metoder istället för att skriva om logiken:

def new_list(self):
    return MultiplicationList(self.get_incorrect())
💡
Varför återanvända? Specen säger ofta "det ger avdrag att lösa något på nytt som redan finns". Har du redan en metod som hämtar de felaktiga — anropa den. Mindre kod = färre buggar = mer poäng.
→ STATISTIK (räkna förekomster)
"hur många gånger …", "den mest populära"
# Räkna förekomster i ett dict
counts = {}
for x in alla:
    counts[x.name] = counts.get(x.name, 0) + 1

# Den med högst antal (None om tomt)
if not counts:
    return None
return max(counts, key=counts.get)

7Tolka en metodbeskrivning — helt exempel

Recepten i avsnitt 6 säger vad returtypen kräver. Men texten under varje metod gömmer ofta två saker till: den refererar tillbaka till egenskaperna (vad betyder "löst"?) och den innehåller nyckelord som blir räkneoperationer. Vi avkodar hela klassen ProblemResult metod för metod.

Först — vad egenskaperna betyder (definieras högst upp)
# attempts: antal försök, börjar på 0
# solved_time: minut då löst, eller -1 om OLÖST  ← viktig konvention
def __init__(self):
    self.attempts = 0
    self.solved_time = -1
💡
Lägg konventionen på minnet: solved_time == -1 betyder "olöst". Metoderna nedan refererar till det utan att upprepa det — du måste komma ihåg definitionen från Egenskaper:.
is_solved() → bool
"Returnerar True om uppgiften är löst, annars False."

Returtyp bool + "om" → returnera ett villkor. Men hur vet vi att den är löst? Det står inte här — det stod i egenskaperna: löst = solved_time är inte -1.

def is_solved(self) -> bool:
    return self.solved_time != -1
💡
Lärdom: en metodbeskrivning står aldrig ensam. "Löst" är ett ord vars betydelse definierades i Egenskaper:. Läs alltid hela specen innan du tolkar en enskild metod.
total_time() → int
"…total tid… inklusive tidstillägg… 20 minuter per ej godkänt försök. Om uppgiften ej är löst returneras 0."

Plocka isär texten mening för mening — varje fras är en kodrad:

"Om uppgiften ej är löst returneras 0"specialfall först: if not …: return 0
"inklusive tidstillägg"grundtid + ett tillägg
"20 minuter per ej godkänt försök""per" = 20 * (antal ej godkända)
"ej godkända försök"alla utom det lyckade: attempts - 1
def total_time(self) -> int:
    if not self.is_solved():        # återanvänd metoden ovan!
        return 0
    extra = 20 * (self.attempts - 1)  # 20 min per misslyckat försök
    return self.solved_time + extra
💡
Två nycklar: (1) "om … returneras X" är nästan alltid en guard som ska stå först. (2) "per" betyder multiplikation. Och varför attempts - 1? Är uppgiften löst gjordes det sista försöket rätt — alla innan var misslyckade.
Vi anropar self.is_solved() istället för att skriva solved_time != -1 igen. Återanvänd dina egna metoder — det är både kortare och det specen belönar.
__str__() → str
"…antal försök följt av antingen tidpunkten… eller - (om ej löst). Separerade med mellanslag."

När en beskrivning är klurig — luta dig mot exemplen. De låser formatet exakt:

Exempel: "2 -" (två misslyckade)antal, mellanslag, bindestreck
Exempel: "3 77" (löst på minut 77)antal, mellanslag, tidpunkt
"antingen … eller …"if/else på den andra delen
def __str__(self) -> str:
    if self.is_solved():
        return f"{self.attempts} {self.solved_time}"   # "3 77"
    return f"{self.attempts} -"                    # "2 -"
💡
Exemplen är facit för formatet. Räkna tecknen: ett mellanslag mellan delarna, - (inte "ej löst") när det är olöst. "antingen A eller B" → A är alltid med, B avgörs av ett villkor.
🧩
Sammanfattning av tolkningsknepen: läs hela specen (ord definieras i Egenskaper:) · "om … returneras X" = guard först · "per" = multiplicera · "inklusive/plus" = addera · "antingen … eller" = if/else · exempel låser formatet tecken för tecken · återanvänd egna metoder.

8Privata attribut — och varför self._x

🚨
Tentans regel (gäller alltid): "Attribut ska vara privata om inget annat anges. Egenskaper listade i klasspecifikationer ska alltid vara publika." Privat = standard. Publikt = bara det som står under Egenskaper: i specen.
Beslutet: står attributet under "Egenskaper:"?
JA — listat under Egenskaper:publikt  self.x (inget understreck)
NEJ — ditt eget interna hjälp-attributprivat  self._x (ett understreck)
💡
Varför skillnaden? "Egenskaper" i specen är klassens publika kontrakt — sådant som omvärlden (och testfallen) får läsa direkt, t.ex. booking.free_seats eller game.remaining_words. Allt annat du själv inför för att lösa uppgiften (räknare, hjälp-listor, historik) är klassens interna grejer och ska döljas.
Så här ser det ut i praktiken
# TableBooking: specen listar total_seats, free_seats, bookings
# under "Egenskaper:" → alla tre är PUBLIKA
def __init__(self, total_seats):
    self.total_seats = total_seats   # publik (egenskap)
    self.free_seats = total_seats    # publik (egenskap)
    self.bookings = set()           # publik (egenskap)

# Restaurant: specen listar INGA egenskaper → allt PRIVAT
def __init__(self):
    self._orders = {}                 # privat (eget val)
    self._all_orders = []             # privat (eget val)

Använd sedan samma namn (med eller utan _) konsekvent i alla metoder inne i klassen:

def total_sales(self):
    total = 0
    for o in self._all_orders:   # _ här också
        total += o.total_price()
    return total
💡
Varför ett understreck? Det signalerar inkapsling: "rör inte det här utifrån". Omvärlden ska prata med objektet via dess metoder (add_order, total_sales), inte peta direkt på den interna datan. Då kan du byta ut det inre utan att något utanför går sönder.
⚠️
Statusflaggor och hjälp-strukturer du själv hittar på (t.ex. self._correct, self._used_indexes) står aldrig under "Egenskaper:" → de ska vara privata.

9Huvudprogram & loopar

Sista pusselbiten binder ihop klasserna med användaren. Receptet är alltid detsamma: fråga → loopa tills klart → skriv slutmeddelande.

STEG 1
Hämta indata med input()
# Tal? Glöm inte int(...) — input ger alltid en sträng
low = int(input("Skriv in den lägsta tabellen: "))

# Flera ord på en rad? .split() delar på mellanslag
words = input("Ange fyra ord: ").split()
STEG 2
Loopa tills "klart"-villkoret är uppfyllt

Använd while not <klar>: — fråga en metod om spelet är slut.

while not game.is_finished():
        print("\nKvarvarande ord:")
        print(sorted(game.remaining_words))   # sortera innan utskrift!
        words = input("Ange fyra ord: ").split()
        result = game.guess(words)
        if result == -1:
            print("Felaktig inmatning av grupp.")
        elif result == 4:
            print("Rätt grupp!")
        else:
            print(f"Fel grupp. {result} av fyra var rätt.")
    print("Alla grupper hittade!")
💡
Varför lägga logiken i klassen? Huvudprogrammet ska bara sköta in/utmatning. Frågan "är spelet slut?" och "är gissningen rätt?" är klassens ansvar. Då blir loopen kort och lätt att få rätt.
🚨
Utskriftstexterna avgör poängen. Kopiera varje text ordagrant från uppgiften (de ligger ofta som kommentarer att klistra in). Ett extra mellanslag eller liten bokstav = underkänt testfall. Kvarvarande element sorteras alltid före utskrift, annars matchar inte testet.

10Snabbreferens — kodfragment

Summera
sum(x.price for x in items)
Högst värde i dict
max(d, key=d.get)   # nyckeln
max(d.values())    # värdet
Sortera
sorted(lista)
sorted(lista, key=lambda x: x.namn)
Räkna förekomster
d[k] = d.get(k, 0) + 1
Undvik KeyError
d.get(nyckel, 0)   # 0 om saknas
Mängd-operationer
a & b   # gemensamma
a | b   # union
a - b   # differens
Input → typ
x = int(input("…"))
ord = input("…").split()
Slump
import random
random.choice(lista)
random.shuffle(lista)
Fyll lista med nollor
[0] * antal   # [0, 0, 0]
Generera utan dubbletter
for i in range(low, high+1):
  for j in range(i, high+1):  # j från i!
    ...

11Checklista på tentan

  • Läs hela specen för uppgiften innan du skriver ett tecken.
  • Titta på returtypen (-> ???) → slå upp receptet i avsnitt 2 & 6.
  • Konstruktorn: parametrar → self.x = x; egna egenskaper → tom [] {} set() 0 False.
  • Valde du datastruktur efter specens ord (unika→set, koppla→dict, ordning→list)?
  • Privat är standard → self._x. Bara det som står under "Egenskaper:" är publikt (self.x).
  • Lös metoderna i ordning — testfallen bygger på varandra.
  • Fastnar du >5 min? pass och gå vidare — delpoäng finns.
  • Återanvänd dina egna metoder istället för att kopiera logik (ger avdrag annars).
  • __str__ och utskrifter: matcha formatet tecken för tecken. "Show Differences".
  • Element som skrivs ut i ett program: sortera först (sorted()).
  • Returnerar metoden None? Ändra self, skriv ingen return.
  • Kör din lösning mentalt mot exempelkörningen i uppgiften.