Eksamen: REA3049-PY | Semester: Vår 2025 | Varighet: 5 timer | Tema: Pseudokode, OOP, Caesar-chiffer, bibliotek, friluftsdatasett, skogbrann-simulering
sjekkTall(6)? Pseudokoden bygger en streng der tall delelig med både 2 og 3 erstattes med "TrommeLom", bare 2 med "Tromm", bare 3 med "Lom", og resten beholdes som tall.
| i | i % 2 | i % 3 | Hvilken gren? | Lagt til |
|---|---|---|---|---|
| 1 | 1 | 1 | else | "1 " |
| 2 | 0 | 2 | else if i%2==0 | "Tromm " |
| 3 | 1 | 0 | else if i%3==0 | "Lom " |
| 4 | 0 | 1 | else if i%2==0 | "Tromm " |
| 5 | 1 | 2 | else | "5 " |
| 6 | 0 | 0 | første if (begge) | "TrommeLom " |
Sammensetningen blir "1 Tromm Lom Tromm 5 TrommeLom". Dette er en variant av det klassiske FizzBuzz-problemet.
Begrunnelse: Komposisjon er en sterk «inneholder»-relasjon (whole–part) der den indre delens livssyklus er bundet til den ytre helheten. Når den ytre delen ødelegges, ødelegges også de indre delene. Dette skiller komposisjon fra:
Begrunnelse: Polymorfisme («mange former») betyr at samme metodenavn kan ha forskjellig oppførsel avhengig av objektets faktiske type. Når du kaller dyr.lyd() på et Hund-objekt, kjøres Hund.lyd(); på et Katt-objekt kjøres Katt.lyd(). De andre prinsippene:
Begrunnelse: Innkapsling (encapsulation) handler nettopp om å skjule den interne implementasjonen og kun eksponere et veldefinert grensesnitt utad. I Python uttrykkes dette gjerne med understrek-prefiks (_navn for «privat», __navn for name-mangling) og getter/setter-metoder eller @property. Innkapsling gir robusthet: implementasjonen kan endres uten å bryte koden som bruker objektet.
behandleTekst(tekst, n) gjør.
Algoritmen er et enkelt Caesar-chiffer tilpasset det norske alfabetet. Den tar inn en tekst og et heltall n, og forskyver hver bokstav n plasser framover i et alfabet på 29 tegn (a–å). Bokstaver som ikke er bokstaver – mellomrom, tall, tegnsetting – blir værende uendret.
Manipuleringsfunksjonen finner posisjonen til hver bokstav i alfabettstrengen, legger til n, og bruker modulo 29 ((posisjon + n) % 29) slik at vi «pakker rundt» når vi går forbi å. Eksempel: med n = 3 blir a til d, og å blir til c (den «runder» tilbake til starten).
Merk en svakhet i pseudokoden: alfabetstrengen inneholder både små og store bokstaver, men store bokstaver står på posisjonene 29–57. Modulo 29 vil derfor «kollapse» store og små bokstaver til samme indeksposisjon. I en god implementasjon bør man behandle store og små bokstaver hver for seg, og bevare opprinnelig store/små bokstaver i resultatet.
n og vise resultatet.
# oppgave5b.py — Caesar-chiffer for norsk alfabet
ALFABET_SMA = "abcdefghijklmnopqrstuvwxyzæøå"
ALFABET_STORE = ALFABET_SMA.upper()
ALFABET_LENGDE = len(ALFABET_SMA) # 29
def manipuler_bokstav(bokstav: str, n: int) -> str:
"""Forskyv én bokstav n plasser framover, med wrap-around."""
if bokstav in ALFABET_SMA:
ny_pos = (ALFABET_SMA.index(bokstav) + n) % ALFABET_LENGDE
return ALFABET_SMA[ny_pos]
if bokstav in ALFABET_STORE:
ny_pos = (ALFABET_STORE.index(bokstav) + n) % ALFABET_LENGDE
return ALFABET_STORE[ny_pos]
return bokstav # tall, mellomrom, tegnsetting beholdes
def behandle_tekst(tekst: str, n: int) -> str:
return "".join(manipuler_bokstav(b, n) for b in tekst)
def main() -> None:
tekst = input("Skriv inn tekst: ")
while True:
try:
n = int(input("Forskyvning n (heltall): "))
break
except ValueError:
print("Ugyldig — skriv et heltall.")
resultat = behandle_tekst(tekst, n)
print(f"Original: {tekst}")
print(f"Kryptert: {resultat}")
print(f"Dekryptert verifisering: {behandle_tekst(resultat, -n)}")
if __name__ == "__main__":
main()
Skriv inn tekst: Hei på degForskyvning n (heltall): 3Original: Hei på degKryptert: Khl rb ghoDekryptert verifisering: Hei på deg
Relasjonen er en assosiasjon (toveis): en Bok kan være utlånt til én Låner, og en Låner kan ha referanser til flere Bok-objekter. Begge klassene eksisterer uavhengig av hverandre (en bok kan stå urørt i hylla; en låner kan finnes uten å ha lånt noe).
+----------------------+ +-------------------------+
| Bok | | Låner |
+----------------------+ +-------------------------+
| - tittel: str | 0..* | - lånerID: int |
| - forfatter: str |<-------------| - lånteBøker: [Bok] |
| - utlånt: Låner|None | 0..1 +-------------------------+
+----------------------+ | + lånBok(b: Bok) |
| + visInfo() | | + leverTilbakeBok(b) |
+----------------------+ +-------------------------+
Kardinalitet: én låner kan låne 0 eller flere bøker (0..*); én bok er utlånt til 0 eller én låner (0..1).
# bibliotek.py — implementasjon av Bok og Låner
class BibliotekFeil(Exception):
"""Base-unntak for bibliotek-relaterte feil."""
class Bok:
def __init__(self, tittel: str, forfatter: str) -> None:
self.tittel = tittel
self.forfatter = forfatter
self.utlant: "Laner | None" = None
def vis_info(self) -> None:
status = (
f"utlånt til {self.utlant.navn} (ID {self.utlant.laner_id})"
if self.utlant
else "tilgjengelig"
)
print(f"'{self.tittel}' av {self.forfatter} — {status}")
def __repr__(self) -> str:
return f"Bok({self.tittel!r}, {self.forfatter!r})"
class Laner:
def __init__(self, laner_id: int, navn: str) -> None:
self.laner_id = laner_id
self.navn = navn
self.lante_boker: list[Bok] = []
def lan_bok(self, bok: Bok) -> None:
if bok.utlant is not None:
raise BibliotekFeil(
f"'{bok.tittel}' er allerede utlånt til {bok.utlant.navn}."
)
bok.utlant = self
self.lante_boker.append(bok)
def lever_tilbake_bok(self, bok: Bok) -> None:
if bok not in self.lante_boker:
raise BibliotekFeil(
f"{self.navn} har ikke lånt '{bok.tittel}'."
)
bok.utlant = None
self.lante_boker.remove(bok)
def __repr__(self) -> str:
return f"Laner({self.laner_id}, {self.navn!r}, {len(self.lante_boker)} bøker)"
# test_bibliotek.py
from bibliotek import Bok, Laner, BibliotekFeil
def test_bibliotek() -> None:
bok1 = Bok("Sofies verden", "Jostein Gaarder")
bok2 = Bok("Naiv. Super.", "Erlend Loe")
bok3 = Bok("Beatles", "Lars Saabye Christensen")
emma = Laner(1, "Emma")
ola = Laner(2, "Ola")
# 1) Lån ut
emma.lan_bok(bok1)
emma.lan_bok(bok2)
bok1.vis_info() # 'Sofies verden' av Jostein Gaarder — utlånt til Emma (ID 1)
assert bok1 in emma.lante_boker
assert bok1.utlant is emma
# 2) Lever tilbake
emma.lever_tilbake_bok(bok1)
bok1.vis_info() # tilgjengelig
assert bok1 not in emma.lante_boker
assert bok1.utlant is None
# 3) Allerede utlånt — skal kaste feil
ola.lan_bok(bok3)
try:
emma.lan_bok(bok3)
except BibliotekFeil as e:
print(f"OK — fanget feil: {e}")
else:
raise AssertionError("Skulle kastet BibliotekFeil")
# 4) Lever tilbake bok som ikke er lånt — skal kaste feil
try:
emma.lever_tilbake_bok(bok3)
except BibliotekFeil as e:
print(f"OK — fanget feil: {e}")
print("Alle tester passert.")
if __name__ == "__main__":
test_bibliotek()
Programmet håndterer to typer feil ved å kaste det egendefinerte unntaket BibliotekFeil:
Ved å definere et eget unntak (i stedet for å bruke ValueError eller Exception) kan kallende kode skille bibliotek-feil fra andre feil med except BibliotekFeil. Testene over bekrefter at både den positive flyten (lån, tilbakelevering) og feilstiene fungerer som forventet.
Innledning
VR-baserte behandlingsverktøy som det Emma deltar i, gir ny mulighet for trygg eksponering, men reiser også etiske spørsmål som bør drøftes før teknologien tas i utbredt bruk. Jeg vil her diskutere to sentrale dilemmaer: (1) personvern og datasikkerhet, og (2) overføringsverdi versus teknologiavhengighet.
Dilemma 1: Personvern og biometri
VR-headset registrerer mer enn bare bilde og lyd: blikkretning, hodebevegelser, hudkonduktans i tilkoblede sensorer og pulsmålinger. For en ungdom med sosial angst betyr det at sensitive data om frykt-respons og kroppslige reaksjoner samles inn. Etter GDPR (artikkel 9) regnes dette som spesielt beskyttede helseopplysninger. Hvem eier dataene – sykehuset, leverandøren av VR-utstyret, eller Emma selv? Brukes de bare til behandling, eller også til å trene maskinlæringsmodeller som kan kommersialiseres? At Emma er mindreårig (16) skjerper kravet til reelt informert samtykke fra både henne og foresatte. Et sterkt argument for bruk er at behandlingen kan hjelpe henne ut av en alvorlig lidelse; mot taler at innsamlede biometriske data, hvis de lekker eller misbrukes, kan følge henne resten av livet.
Dilemma 2: Overføringsverdi versus teknologiavhengighet
Casen sier eksplisitt at Emma «føler seg tryggere i virtuelle situasjoner, men er usikker på hvordan hun vil håndtere virkelige presentasjoner». Dette peker på et grunnleggende spørsmål: fungerer VR-eksponeringen som en stillas mot virkelig mestring, eller blir den et trygt unndrag der man slipper å konfrontere det skarpere ubehaget i en ekte klasse? Forskning på eksponeringsterapi viser at habituering må generaliseres til reelle situasjoner for at angsten skal varig avta. Hvis Emma blir avhengig av VR-rommet, kan teknologien forsterke unngåelsesatferd – det motsatte av målet. Etisk er det viktig at behandlingen designes med en tydelig overgang fra virtuell til reell øvelse, og at framgang måles i hverdagslige situasjoner, ikke kun i simuleringen.
Andre relevante hensyn
Tidlig innsats er normalt et gode – sosial angst rammer ofte i 14–16-årsalderen og blir lettere å behandle tidlig. Men man bør være varsom med å patologisere normal sjenanse hos ungdom. Like tilgang er et tredje moment: VR-utstyr koster penger og krever teknisk kompetanse; en behandlingsform som bare når elever på enkelte skoler skaper sosial ulikhet i helsehjelp.
Konklusjon
VR-teknologi for behandling av sosial angst er etisk forsvarlig dersom den kombineres med streng datakontroll, reell overføring til ekte situasjoner, og likeverdig tilgang. Uten disse rammene risikerer man både personverninngrep og at teknologien blir et komfortabelt unngåelsesrom som forsterker problemet den skulle løse.
# oppgave8.py — friluftsaktiviteter
import csv
from pathlib import Path
DATAFIL = Path("friluftsaktiviteter_2024.csv")
def les_data(filsti: Path = DATAFIL) -> list[dict[str, str]]:
"""Les CSV-fil og returner liste av rader (én per fylke)."""
with filsti.open(encoding="utf-8") as f:
leser = csv.DictReader(f, delimiter=";")
return list(leser)
def aktivitetsnavn(data: list[dict]) -> list[str]:
"""Alle kolonnenavn unntatt 'Fylke' regnes som aktiviteter."""
return [k for k in data[0].keys() if k != "Fylke"]
def totaler_per_aktivitet(data: list[dict]) -> dict[str, int]:
aktiviteter = aktivitetsnavn(data)
return {a: sum(int(rad[a]) for rad in data) for a in aktiviteter}
def vis_totaltabell(totaler: dict[str, int]) -> None:
print(f"\n{'Aktivitet':<28}{'Total':>10}")
print("-" * 38)
for aktivitet, total in sorted(totaler.items(), key=lambda x: x[1], reverse=True):
print(f"{aktivitet:<28}{total:>10,}".replace(",", " "))
def vis_fylke(data: list[dict], fylke: str) -> None:
rad = next((r for r in data if r["Fylke"].lower() == fylke.lower()), None)
if rad is None:
print(f"Fylket '{fylke}' ble ikke funnet i datasettet.")
return
aktiviteter = aktivitetsnavn(data)
par = sorted(((a, int(rad[a])) for a in aktiviteter), key=lambda x: x[1])
total = sum(antall for _, antall in par) or 1 # unngå deling på 0
print(f"\nAktiviteter i {rad['Fylke']} (sortert stigende):")
print(f"{'Aktivitet':<28}{'Antall':>8}{'Prosent':>10}")
print("-" * 46)
for aktivitet, antall in par:
print(f"{aktivitet:<28}{antall:>8}{antall/total*100:>9.1f} %")
import matplotlib.pyplot as plt
def topp_tre_diagram(data: list[dict], fylke: str) -> None:
rad = next((r for r in data if r["Fylke"].lower() == fylke.lower()), None)
if rad is None:
print(f"Fylket '{fylke}' ble ikke funnet.")
return
aktiviteter = aktivitetsnavn(data)
topp3 = sorted(
((a, int(rad[a])) for a in aktiviteter),
key=lambda x: x[1], reverse=True,
)[:3]
navn = [n for n, _ in topp3]
verdier = [v for _, v in topp3]
plt.figure(figsize=(8, 5))
plt.bar(navn, verdier, color=["#2196F3", "#4CAF50", "#FFC107"])
plt.title(f"Topp 3 friluftsaktiviteter i {rad['Fylke']} (2024)")
plt.ylabel("Antall deltakere")
plt.xticks(rotation=15)
plt.tight_layout()
plt.savefig(f"topp3_{rad['Fylke'].replace(' ', '_')}.png", dpi=120)
plt.show()
def main() -> None:
data = les_data()
vis_totaltabell(totaler_per_aktivitet(data))
fylke = input("\nVelg et fylke (f.eks. Oslo): ").strip()
vis_fylke(data, fylke)
topp_tre_diagram(data, fylke)
if __name__ == "__main__":
main()
oppgave8/ ├── oppgave8.py ├── friluftsaktiviteter_2024.csv └── topp3_Oslo.png (eksempelutdata)
# skogbrann.py — objektorientert simulering av skogbrann
from __future__ import annotations
import random
import tkinter as tk
from enum import Enum
from dataclasses import dataclass
# --- Konstanter ---
RUTER = 40
CELLE_PX = 14
P_VOKS = 0.003 # 0,3 % sjanse for at en tom celle blir et tre
P_LYN = 0.0003 # 0,03 % sjanse for at et tre treffes av lyn
TICK_NORMAL_MS = 100 # normal vekstrate
TICK_BRANN_MS = 40 # raskere mens det brenner
class Tilstand(Enum):
TOM = "tom"
TRE = "tre"
BRANN = "brann"
@dataclass
class Celle:
rad: int
kol: int
tilstand: Tilstand = Tilstand.TOM
class Skog:
def __init__(self, rader: int = RUTER, kolonner: int = RUTER) -> None:
self.rader = rader
self.kolonner = kolonner
self.rutenett: list[list[Celle]] = [
[Celle(r, k) for k in range(kolonner)]
for r in range(rader)
]
def naboer(self, c: Celle) -> list[Celle]:
"""4-naboer (von Neumann). Bytt til 8 hvis diagonal-spredning ønskes."""
retninger = [(-1, 0), (1, 0), (0, -1), (0, 1)]
resultat = []
for dr, dk in retninger:
r, k = c.rad + dr, c.kol + dk
if 0 <= r < self.rader and 0 <= k < self.kolonner:
resultat.append(self.rutenett[r][k])
return resultat
def brenner_noe(self) -> bool:
return any(c.tilstand == Tilstand.BRANN for rad in self.rutenett for c in rad)
def tick(self) -> None:
# Beregn neste tilstand uten å forstyrre nåværende.
ny: list[list[Tilstand]] = [
[c.tilstand for c in rad] for rad in self.rutenett
]
det_brenner = self.brenner_noe()
for rad in self.rutenett:
for c in rad:
if c.tilstand == Tilstand.BRANN:
# Brennende celle blir tom; antenner naboer som er trær
ny[c.rad][c.kol] = Tilstand.TOM
for n in self.naboer(c):
if n.tilstand == Tilstand.TRE:
ny[n.rad][n.kol] = Tilstand.BRANN
elif c.tilstand == Tilstand.TRE:
# Lyn kan slå ned (uavhengig av om brann pågår)
if random.random() < P_LYN:
ny[c.rad][c.kol] = Tilstand.BRANN
elif c.tilstand == Tilstand.TOM and not det_brenner:
# Trær vokser bare når det IKKE brenner — tidsskala-kravet
if random.random() < P_VOKS:
ny[c.rad][c.kol] = Tilstand.TRE
for rad in self.rutenett:
for c in rad:
c.tilstand = ny[c.rad][c.kol]
class SkogVisning:
FARGER = {
Tilstand.TOM: "#e8d7b9", # sandfarget bakgrunn
Tilstand.TRE: "#2e7d32", # grønn
Tilstand.BRANN: "#e53935", # rød
}
def __init__(self, skog: Skog) -> None:
self.skog = skog
self.root = tk.Tk()
self.root.title("Skogbrann-simulering")
self.canvas = tk.Canvas(
self.root,
width=skog.kolonner * CELLE_PX,
height=skog.rader * CELLE_PX,
bg=self.FARGER[Tilstand.TOM],
highlightthickness=0,
)
self.canvas.pack()
self.celleids: dict[tuple[int, int], int] = {}
self._tegn_init()
def _tegn_init(self) -> None:
for rad in self.skog.rutenett:
for c in rad:
x1, y1 = c.kol * CELLE_PX, c.rad * CELLE_PX
rid = self.canvas.create_rectangle(
x1, y1, x1 + CELLE_PX, y1 + CELLE_PX,
fill=self.FARGER[c.tilstand], outline="",
)
self.celleids[(c.rad, c.kol)] = rid
def oppdater(self) -> None:
for rad in self.skog.rutenett:
for c in rad:
self.canvas.itemconfig(
self.celleids[(c.rad, c.kol)],
fill=self.FARGER[c.tilstand],
)
def kjor(self) -> None:
def tick():
self.skog.tick()
self.oppdater()
forsinkelse = TICK_BRANN_MS if self.skog.brenner_noe() else TICK_NORMAL_MS
self.root.after(forsinkelse, tick)
self.root.after(TICK_NORMAL_MS, tick)
self.root.mainloop()
def main() -> None:
skog = Skog()
SkogVisning(skog).kjor()
if __name__ == "__main__":
main()
Skog håndterer simuleringslogikken (modell), SkogVisning håndterer Tkinter (view) — separation of concerns.ny-rutenettet før vi skriver tilbake, slik at en celle som blir antent i samme trinn ikke smitter videre i samme tick (ellers «løper» brannen tvers over kartet på ett trinn).Foreslått mappestruktur (kandidatnummer som arkivnavn, f.eks. 123456.zip):
123456/
├── oppgave5b/
│ └── caesar_chiffer.py
├── oppgave6/
│ ├── bibliotek.py
│ └── test_bibliotek.py
├── oppgave8/
│ ├── oppgave8.py
│ ├── friluftsaktiviteter_2024.csv
│ └── topp3_Oslo.png
├── oppgave9/
│ └── skogbrann.py
└── README.md (kort om hvordan man kjører hver fil)