Eksamen: REA3049-PY | Semester: Høst 2023 | Tema: Pseudokode-grunnlag, OOP-prinsipper, billettsystem-flytdiagram, nest-størst-algoritme, IoT-personvern, YouTube-datasett, Manic Mansion-spill
Pseudokode er nettopp ikke kjørbar — den ligner på menneskespråk, har uformell syntaks og brukes i planleggingsfasen. Datamaskinen forstår bare et formelt programmeringsspråk. Pseudokoden må «oversettes» til Python, JavaScript e.l. for å kjøre.
For-løkker passer for kjente, faste antall iterasjoner (over en sekvens, eller med teller). While-løkker passer når avslutningsbetingelsen er dynamisk eller ukjent på forhånd. De andre påstandene er feil: for-løkker kan iterere over alle iterables (ikke bare tall), while-løkker har ikke fast antall, og while-løkker kan absolutt bruke en teller.
OOP samler data (felter) og oppførsel (metoder) i objekter, slik at programmet modellerer virkeligheten i form av selvstendige enheter som kan kommunisere. Det er motpolen til prosedural/funksjonell programmering der data og funksjoner er adskilt.
| Alt. | Pseudokode | Skriver ut | Riktig? |
|---|---|---|---|
| 1 | SET i TO 1; FOR i ≤ 5: PRINT i | 1, 2, 3, 4, 5 | ✅ |
| 2 | SET i TO 1; WHILE i < 5: PRINT i; INCREMENT i | 1, 2, 3, 4 (mangler 5) | ❌ |
| 3 | SET i TO 0; FOR i ≤ 4: PRINT i+1 | 1, 2, 3, 4, 5 | ✅ |
| 4 | SET i TO 1; WHILE i ≤ 5: PRINT i; INCREMENT i BY 2 | 1, 3, 5 (kun oddetall) | ❌ |
┌──────────┐
│ START │
└────┬─────┘
▼
┌──────────────┐
│ LES alder │
└────┬─────────┘
▼
╱──────────╲
╱ alder ≤ 15? ╲────Ja──▶ DISPLAY "Barnebillett: 30 kr" ──┐
╲──────────╱ │
Nei │
▼ │
╱──────────╲ │
╱ alder ≥ 67? ╲────Ja──▶ DISPLAY "Pensjonistbillett: 35 kr"─┤
╲──────────╱ │
Nei │
▼ │
DISPLAY "Voksenbillett: 50 kr" │
│ │
└──────────────────┬──────────────────────────────────┘
▼
┌──────────┐
│ SLUTT │
└──────────┘
Tilsvarende pseudokode:
READ alder
IF alder <= 15
DISPLAY "Barnebillett: 30 kr"
ELSE IF alder >= 67
DISPLAY "Pensjonistbillett: 35 kr"
ELSE
DISPLAY "Voksenbillett: 50 kr"
ENDIF
Test pensjonist-grensen først hvis du tester etter «alder ≥ 16» (ellers vil en 80-åring matche voksen-grenen). Den enkleste rekkefølgen er barn → pensjonist → voksen (else).
| Alt. | Strategi | Korrekt? | Hvorfor |
|---|---|---|---|
| 1 | Finn størst, fjern den, finn nest-størst | ❌ | Hvis flere har samme maks-verdi (f.eks. [9, 9, 5]), vil «fjern størst» bare fjerne én 9-er, og 9 returneres som «nest størst» — i strid med kravet. |
| 2 | Initialiser med to første verdier, oppdater nøye | ✅ | Sjekken tall NOT EQUAL TO størst sikrer at duplikater av maks ikke regnes som nest-størst. |
| 3 | Kun én løkke, oppdater størst og nest-størst | ❌ | Mangler sjekk på likhet — hvis listen er [9, 9, 5], setter den nest-størst = 9. |
| 4 | Sorter synkende, finn første som er ulik forrige | ✅ | Etter sortering [9, 9, 5] er svaret elementet etter alle 9-erne — altså 5. Krav om «ingen av maks regnes» er oppfylt. |
Tidskompleksitet: Løsning 2 er O(n) — den traverserer listen én gang. Løsning 4 er O(n log n) på grunn av sorteringen. For store lister er løsning 2 betydelig raskere.
Plasskompleksitet: Løsning 2 bruker konstant ekstra plass (to variabler). Løsning 4 trenger plass til den sorterte listen, eller endrer originalen — som kan være uønsket.
Lesbarhet: Løsning 4 er enklere å forstå og kortere å skrive — sortering er en velkjent operasjon. Løsning 2 har mer betingelseslogikk (initialisering, swap, doble sjekker) og er lettere å gjøre feil i.
Konklusjon: Løsning 4 vinner på lesbarhet og kompakthet. Løsning 2 vinner på effektivitet, særlig når listen er stor eller man ikke ønsker å endre den. For en typisk skoleeksempel med få elementer er forskjellen i ytelse uvesentlig, og løsning 4 er den jeg ville valgt for tydelig kode.
# nest_storst.py — to løsninger som finner nest-største unike verdi
from typing import Sequence
def nest_storst_lineaer(tall: Sequence[float]) -> float | None:
"""Løsning 2 — én pass, O(n)."""
if len(tall) < 2:
return None
storst = nest_storst = float("-inf")
for t in tall:
if t > storst:
nest_storst = storst
storst = t
elif t < storst and t > nest_storst:
nest_storst = t
return nest_storst if nest_storst != float("-inf") else None
def nest_storst_sortert(tall: Sequence[float]) -> float | None:
"""Løsning 4 — sorter og hopp over duplikater, O(n log n)."""
if len(tall) < 2:
return None
sortert = sorted(tall, reverse=True)
storst = sortert[0]
for t in sortert[1:]:
if t != storst:
return t
return None # alle elementene var like
if __name__ == "__main__":
eksempler = [
[9, 9, 5, 3], # forventet: 5
[1, 2, 3, 4, 5], # forventet: 4
[7], # forventet: None
[3, 3, 3], # forventet: None (alle like)
]
for e in eksempler:
print(f"{e!s:<18} → lineær={nest_storst_lineaer(e)}, sortert={nest_storst_sortert(e)}")
IoT-enheter samler ofte sensitive opplysninger (helse, lokasjon, vaner). Brudd på personvernregelverket kan føre til store bøter etter GDPR (opp til 4 % av global omsetning) og omdømmetap. Et forsikringsselskap som inngår samarbeid uten risikovurdering, eksponeres for betydelig juridisk og økonomisk risiko.
Endring: Den mest gjennomgripende endringen i finanssektoren det siste tiåret er overgangen til digitale betalingsløsninger — Vipps, BankID, mobilbank — som har erstattet både fysisk kontant og papirgiro for de aller fleste hverdagstransaksjoner. Norge er i dag blant verdens mest «kontantløse» økonomier; under 3 % av betalingene skjer med kontanter (Norges Bank, 2023).
Endringen er drevet av flere IT-trender: smarttelefonens utbredelse, sikker autentisering med BankID, åpne API-er gjennom PSD2-direktivet, og maskinlæringsbaserte system for svindeldeteksjon. Tradisjonelle banker har gått fra fysiske filialer til app-styrt selvbetjening, og nye fintech-aktører (Klarna, Revolut, Wise) konkurrerer direkte med etablerte banker.
Dilemmaet: Når alle transaksjoner er digitale, etterlater hver kjøp en datasti — hva, hvor, når og hvor mye. Bankene ser dermed ikke bare hvilke varer du kjøper, men kan også slutte seg til helsetilstand (apotek-kjøp), politisk engasjement (donasjoner), forhold (hotell-kjøp utenfor hjemstedet) og hele livsstilen din.
Spenning: Disse dataene er nyttige — de gjør det mulig å avsløre svindel, tilby personlige råd og forebygge gambling-avhengighet. Men de er også svært sensitive. Hvis dataene lekker, selges, eller kobles med andre datasett, kan profileringen brukes til diskriminering (kredittavslag basert på «livsstil»), målrettet manipulasjon (mikrotargetert reklame som utnytter sårbarheter), eller statlig overvåkning.
GDPR og dataminimering: Personvernforordningen krever at banker bare lagrer det de trenger til avtalt formål — men «svindeldeteksjon» og «kunderelasjon» er bredt nok til at lite effektivt begrenses i praksis. Dataportabilitet (PSD2) gir kunden rett til å overføre data til andre tilbydere, men gjør samtidig at flere aktører får tilgang til transaksjonshistorikken.
Vurdering: Den kontantløse økonomiens bekvemmelighet kommer med en pris i form av redusert anonymitet. Et velregulert system med streng tilsynsmyndighet (Datatilsynet, Finanstilsynet) kan dempe risikoen, men aldri fjerne den. Som forbruker er det viktig å være bevisst om at hver Vipps-betaling tilfører en datapost som potensielt eksisterer for alltid.
# oppgave11.py — analyse av YouTube-datasett
"""Forberedelse av datasettet:
- Datasettet er kodet med ukjent tegnsett. Vi prøver utf-8, deretter latin-1.
- Numeriske felter er kodet som tekst i CSV; vi konverterer der det kreves.
- Tomme/feilede land-felter ignoreres."""
import csv
from collections import Counter, defaultdict
from pathlib import Path
DATAFIL = Path("youtube.csv")
def les_data(filsti: Path = DATAFIL) -> list[dict]:
for enc in ("utf-8", "utf-8-sig", "latin-1"):
try:
with filsti.open(encoding=enc) as f:
return list(csv.DictReader(f, delimiter=","))
except UnicodeDecodeError:
continue
raise RuntimeError("Klarte ikke å lese filen i kjente tegnsett.")
def til_int(s: str) -> int:
try:
return int(float(s.replace(",", "").strip()))
except (ValueError, AttributeError):
return 0
def topp_ti_land(data: list[dict]) -> list[tuple[str, int]]:
teller: Counter[str] = Counter()
for r in data:
land = (r.get("Country") or r.get("country") or "").strip()
if land and land.lower() != "nan":
teller[land] += 1
return teller.most_common(10)
def snitt_per_land(data: list[dict], land: list[str]) -> dict[str, tuple[float, float]]:
sum_abo: defaultdict[str, int] = defaultdict(int)
sum_views: defaultdict[str, int] = defaultdict(int)
antall: defaultdict[str, int] = defaultdict(int)
landset = set(land)
for r in data:
l = (r.get("Country") or r.get("country") or "").strip()
if l in landset:
sum_abo[l] += til_int(r.get("subscribers", "0"))
sum_views[l] += til_int(r.get("video views", "0"))
antall[l] += 1
return {
l: (sum_abo[l] / antall[l] if antall[l] else 0,
sum_views[l] / antall[l] if antall[l] else 0)
for l in land
}
def main() -> None:
data = les_data()
topp = topp_ti_land(data)
print("\nTopp 10 land etter antall YouTube-kanaler:")
print(f"{'Rang':<6}{'Land':<20}{'Kanaler':>10}")
for i, (land, antall) in enumerate(topp, start=1):
print(f"{i:<6}{land:<20}{antall:>10}")
snitt = snitt_per_land(data, [l for l, _ in topp])
print("\nGjennomsnitt per kanal i topp 10:")
print(f"{'Land':<20}{'Snitt abonnenter':>20}{'Snitt visninger':>20}")
for land, _ in topp:
abo, views = snitt[land]
print(f"{land:<20}{abo:>20,.0f}{views:>20,.0f}".replace(",", " "))
if __name__ == "__main__":
main()
En naturlig OO-modell består av en abstrakt Spillobjekt-klasse med posisjon og størrelse, og fire spesialiseringer:
Menneske — styres av piltaster, blokkeres av hindringer/kanter, har variabel hastighet (faller når sau bæres).Spokelse — beveger seg konstant i en tilfeldig retning, reflekteres ved kanter og frisone-grenser, ignorerer hindringer.Hindring — statisk objekt, blokkerer Menneske men ikke Spokelse.Sau — statisk inntil bæres; kontaktes Menneske → fjernes/festes.I tillegg har vi en Spillebrett-klasse som holder rede på alle objekter, oppdager kollisjoner, håndterer venstre/høyre frisone, og kjører spilløkken med poengtelling. Modellen viser polymorfisme (hver subklasse implementerer egen tegn() og oppdater()) og innkapsling (objektene eksponerer kun det grensesnittet spillebrettet trenger).
# manic_mansion.py — Pygame-implementasjon
# pip install pygame
from __future__ import annotations
import random
import pygame
# --- Konstanter ---
BREDDE, HOEYDE = 800, 600
FRISONE = 80 # bredde på frisonene venstre/høyre
FPS = 60
MENNESKE_FART = 4.0
SAU_BREMSEFAKTOR = 0.5
SPOKELSE_FART = 3.0
START_SPOKELSER = 1
START_HINDRINGER = 3
START_SAUER = 3
class Spillobjekt:
def __init__(self, x: float, y: float, storrelse: int, farge: tuple) -> None:
self.x, self.y = x, y
self.storrelse = storrelse
self.farge = farge
def rect(self) -> pygame.Rect:
s = self.storrelse
return pygame.Rect(int(self.x - s / 2), int(self.y - s / 2), s, s)
def tegn(self, skjerm) -> None:
pygame.draw.rect(skjerm, self.farge, self.rect())
class Menneske(Spillobjekt):
def __init__(self, x: float, y: float) -> None:
super().__init__(x, y, 28, (26, 43, 74)) # mørk blå
self.bærer_sau = False
self.poeng = 0
self.dx, self.dy = 0.0, 0.0
def fart(self) -> float:
return MENNESKE_FART * (SAU_BREMSEFAKTOR if self.bærer_sau else 1.0)
def oppdater(self, taster, hindringer: list[Spillobjekt]) -> None:
f = self.fart()
nx, ny = self.x, self.y
if taster[pygame.K_LEFT] or taster[pygame.K_a]: nx -= f
if taster[pygame.K_RIGHT] or taster[pygame.K_d]: nx += f
if taster[pygame.K_UP] or taster[pygame.K_w]: ny -= f
if taster[pygame.K_DOWN] or taster[pygame.K_s]: ny += f
# Begrens innenfor spillebrett
s = self.storrelse / 2
nx = max(s, min(BREDDE - s, nx))
ny = max(s, min(HOEYDE - s, ny))
# Hindringskollisjon — beveg én akse av gangen for «glide»-følelse
forrige_x, forrige_y = self.x, self.y
self.x = nx
if any(self.rect().colliderect(h.rect()) for h in hindringer):
self.x = forrige_x
self.y = ny
if any(self.rect().colliderect(h.rect()) for h in hindringer):
self.y = forrige_y
class Spokelse(Spillobjekt):
def __init__(self, x: float, y: float) -> None:
super().__init__(x, y, 24, (229, 115, 115))
vinkel = random.uniform(0, 2 * 3.14159)
import math
self.dx = math.cos(vinkel) * SPOKELSE_FART
self.dy = math.sin(vinkel) * SPOKELSE_FART
def oppdater(self) -> None:
self.x += self.dx
self.y += self.dy
# Reflekter på kanter av brett
if self.x < self.storrelse / 2 or self.x > BREDDE - self.storrelse / 2:
self.dx = -self.dx
if self.y < self.storrelse / 2 or self.y > HOEYDE - self.storrelse / 2:
self.dy = -self.dy
# Reflekter på frisone-grensene (spøkelser holdes ute av frisonene)
if self.x < FRISONE + self.storrelse / 2:
self.dx = abs(self.dx)
if self.x > BREDDE - FRISONE - self.storrelse / 2:
self.dx = -abs(self.dx)
class Hindring(Spillobjekt):
def __init__(self, x: float, y: float) -> None:
super().__init__(x, y, 36, (120, 100, 80))
class Sau(Spillobjekt):
def __init__(self, x: float, y: float) -> None:
super().__init__(x, y, 22, (240, 240, 230))
class Spill:
def __init__(self) -> None:
pygame.init()
self.skjerm = pygame.display.set_mode((BREDDE, HOEYDE))
pygame.display.set_caption("Manic Mansion")
self.klokke = pygame.time.Clock()
self.font = pygame.font.SysFont("Helvetica", 20)
self.menneske = Menneske(40, HOEYDE / 2)
self.spokelser: list[Spokelse] = []
self.hindringer: list[Hindring] = []
self.sauer: list[Sau] = []
for _ in range(START_SPOKELSER): self._ny_spokelse()
for _ in range(START_HINDRINGER): self._ny_hindring()
for _ in range(START_SAUER): self._ny_sau()
self.tapt = False
def _tilfeldig_midten(self) -> tuple[int, int]:
return random.randint(FRISONE + 30, BREDDE - FRISONE - 30), random.randint(30, HOEYDE - 30)
def _ny_spokelse(self) -> None:
x, y = self._tilfeldig_midten()
self.spokelser.append(Spokelse(x, y))
def _ny_hindring(self) -> None:
x, y = self._tilfeldig_midten()
self.hindringer.append(Hindring(x, y))
def _ny_sau(self) -> None:
x = random.randint(BREDDE - FRISONE + 10, BREDDE - 20)
y = random.randint(20, HOEYDE - 20)
self.sauer.append(Sau(x, y))
def _handter_kollisjoner(self) -> None:
m_rect = self.menneske.rect()
# Spøkelse → tap
if any(m_rect.colliderect(s.rect()) for s in self.spokelser):
self.tapt = True
return
# Sau → plukke opp
if not self.menneske.bærer_sau:
for sau in list(self.sauer):
if m_rect.colliderect(sau.rect()):
self.sauer.remove(sau)
self.menneske.bærer_sau = True
break
# Tilbake i venstre frisone med sau → poeng
if self.menneske.bærer_sau and self.menneske.x < FRISONE:
self.menneske.bærer_sau = False
self.menneske.poeng += 1
self._ny_sau()
self._ny_spokelse()
self._ny_hindring()
# Treff annen sau mens man bærer en → tap
if self.menneske.bærer_sau and any(m_rect.colliderect(s.rect()) for s in self.sauer):
self.tapt = True
def kjor(self) -> None:
kjorer = True
while kjorer:
for e in pygame.event.get():
if e.type == pygame.QUIT:
kjorer = False
if not self.tapt:
taster = pygame.key.get_pressed()
self.menneske.oppdater(taster, self.hindringer)
for sp in self.spokelser:
sp.oppdater()
self._handter_kollisjoner()
self.skjerm.fill((232, 215, 185))
pygame.draw.rect(self.skjerm, (200, 220, 200), (0, 0, FRISONE, HOEYDE))
pygame.draw.rect(self.skjerm, (200, 220, 200), (BREDDE - FRISONE, 0, FRISONE, HOEYDE))
for obj in [*self.hindringer, *self.sauer, *self.spokelser, self.menneske]:
obj.tegn(self.skjerm)
tekst = f"Poeng: {self.menneske.poeng}"
if self.tapt:
tekst += " — TAPT! Trykk på krysset for å avslutte."
self.skjerm.blit(self.font.render(tekst, True, (0, 0, 0)), (10, 10))
pygame.display.flip()
self.klokke.tick(FPS)
pygame.quit()
if __name__ == "__main__":
Spill().kjor()
Spillobjekt og overstyrer tegn() ved behov. Spill-klassen kan iterere over en blandet liste og kalle tegn(self.skjerm) uten å bry seg om type._tilfeldig_midten og prøve på nytt — utelatt for kortere kode.123456/
├── oppgave5/billettpris_flytdiagram.pdf
├── oppgave7c/nest_storst.py
├── oppgave11/
│ ├── oppgave11.py
│ └── youtube.csv
├── oppgave12/manic_mansion.py
└── README.md (krev: pip install pygame)