Eksamen: REA3049-JS | Semester: Høst 2023 | Tema: Pseudokode-grunnlag, OOP-prinsipper, billettsystem-flytdiagram, nest-størst-algoritme, IoT-personvern, YouTube-datasett, Manic Mansion-spill
Pseudokode er ikke kjørbar — den ligner på menneskespråk, har uformell syntaks og brukes i planleggingsfasen.
For-løkker passer for kjente, faste antall iterasjoner. While-løkker passer når avslutningsbetingelsen er dynamisk eller ukjent på forhånd.
OOP samler data (felter) og oppførsel (metoder) i objekter, slik at programmet modellerer virkeligheten i form av selvstendige enheter som kan kommunisere.
| Alt. | Kort beskrivelse | Skriver ut |
|---|---|---|
| 1 | FOR i ≤ 5: PRINT i | 1, 2, 3, 4, 5 ✅ |
| 2 | WHILE i < 5: PRINT i; INCREMENT | 1, 2, 3, 4 (mangler 5) ❌ |
| 3 | FOR i ≤ 4: PRINT i+1 | 1, 2, 3, 4, 5 ✅ |
| 4 | WHILE i ≤ 5: PRINT i; INCREMENT 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 JavaScript-implementasjon (også nyttig som referanse):
function billettpris(alder) {
if (alder <= 15) return "Barnebillett: 30 kr";
if (alder >= 67) return "Pensjonistbillett: 35 kr";
return "Voksenbillett: 50 kr";
}
| Alt. | Strategi | Korrekt? | Hvorfor |
|---|---|---|---|
| 1 | Finn størst, fjern den, finn nest-størst | ❌ | «Fjern størst» fjerner bare én av flere like maks-verdier. |
| 2 | Initialiser med to første verdier, oppdater nøye | ✅ | Sjekken tall ≠ størst sikrer at duplikater ikke regnes som nest-størst. |
| 3 | Én løkke uten likhets-sjekk | ❌ | Mangler sjekk på likhet — i [9, 9, 5] returnerer den 9 som nest-størst. |
| 4 | Sorter, finn første som er ulik forrige | ✅ | Korrekt selv ved duplikater. |
Tidskompleksitet: Løsning 2 er O(n) — én pass. Løsning 4 er O(n log n) på grunn av sortering. For store lister er løsning 2 betydelig raskere.
Plasskompleksitet: Løsning 2 bruker konstant ekstra plass. Løsning 4 trenger plass til den sorterte listen, eller endrer originalen.
Lesbarhet: Løsning 4 er enklere og kortere — sortering er en velkjent operasjon. Løsning 2 har mer betingelseslogikk.
Konklusjon: Løsning 4 vinner på lesbarhet, løsning 2 på effektivitet. For typiske skoleeksempler er løsning 4 mest attraktiv; for store datasett bør man velge løsning 2.
// nest_storst.mjs — to korrekte løsninger
export function nestStorstLineaer(tall) {
if (tall.length < 2) return null;
let storst = -Infinity, nestStorst = -Infinity;
for (const t of tall) {
if (t > storst) {
nestStorst = storst;
storst = t;
} else if (t < storst && t > nestStorst) {
nestStorst = t;
}
}
return nestStorst === -Infinity ? null : nestStorst;
}
export function nestStorstSortert(tall) {
if (tall.length < 2) return null;
const sortert = [...tall].sort((a, b) => b - a);
const storst = sortert[0];
for (const t of sortert.slice(1)) {
if (t !== storst) return t;
}
return null;
}
// Tester
const eksempler = [
[9, 9, 5, 3], // forventet: 5
[1, 2, 3, 4, 5], // forventet: 4
[7], // forventet: null
[3, 3, 3], // forventet: null
];
eksempler.forEach(e =>
console.log(`${JSON.stringify(e).padEnd(18)} → lineær=${nestStorstLineaer(e)}, sortert=${nestStorstSortert(e)}`)
);
IoT-enheter samler ofte sensitive opplysninger. GDPR-brudd kan føre til store bøter (opptil 4 % av global omsetning) og omdømmetap.
Endring: Den mest gjennomgripende endringen i finanssektoren er overgangen til digitale betalingsløsninger — Vipps, BankID, mobilbank — som har erstattet kontant og papirgiro. Norge er blant verdens mest «kontantløse» økonomier; under 3 % av betalinger skjer med kontanter (Norges Bank, 2023).
Endringen er drevet av smarttelefonens utbredelse, sikker autentisering med BankID, åpne API-er gjennom PSD2-direktivet, og maskinlæringsbasert svindeldeteksjon. Tradisjonelle banker har gått fra fysiske filialer til app-styrt selvbetjening, og fintech-aktører (Klarna, Revolut, Wise) konkurrerer direkte med etablerte banker.
Dilemmaet: Når alle transaksjoner er digitale, etterlater hvert kjøp en datasti — hva, hvor, når og hvor mye. Bankene kan slutte seg til helsetilstand (apotek), politisk engasjement (donasjoner), forhold (hotellopphold) og hele livsstilen.
Spenning: Dataene er nyttige for svindelhindring og rådgivning, men også svært sensitive. Lekkasje eller misbruk kan brukes til diskriminering (kredittavslag basert på «livsstil»), målrettet manipulasjon, eller statlig overvåkning.
GDPR og dataminimering: Forordningen krever at banker bare lagrer det de trenger til avtalt formål — men «svindeldeteksjon» og «kunderelasjon» er bredt nok til at lite begrenses i praksis. PSD2 gir kunden rett til å overføre data til andre tilbydere, men gjør samtidig at flere aktører får tilgang.
Vurdering: Den kontantløse økonomiens bekvemmelighet kommer med en pris i form av redusert anonymitet. Et velregulert system kan dempe risikoen, men aldri fjerne den.
// oppgave11.mjs — analyse av YouTube-datasett
/* Forberedelse: datasettet kan være kodet med ukjent tegnsett (UTF-8 eller latin-1).
* Numeriske kolonner er kodet som tekst — vi konverterer der det kreves.
* Tomme/nan land-felter ignoreres. */
import { readFileSync } from "node:fs";
function lesData(filsti = "youtube.csv") {
let tekst;
try { tekst = readFileSync(filsti, "utf-8"); }
catch { tekst = readFileSync(filsti, "latin1"); }
const linjer = tekst.trim().split(/\r?\n/);
const [head, ...rest] = linjer;
const headers = head.split(",");
return rest.map(linje => {
const v = linje.split(",");
return Object.fromEntries(headers.map((h, i) => [h.trim(), (v[i] || "").trim()]));
});
}
function tilTall(s) {
const n = parseFloat((s || "").replace(/,/g, ""));
return Number.isFinite(n) ? n : 0;
}
function toppTiLand(data) {
const teller = new Map();
for (const r of data) {
const land = (r.Country || r.country || "").trim();
if (land && land.toLowerCase() !== "nan") {
teller.set(land, (teller.get(land) || 0) + 1);
}
}
return [...teller.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
}
function snittPerLand(data, land) {
const samletAbo = new Map(), samletViews = new Map(), antall = new Map();
const landset = new Set(land);
for (const r of data) {
const l = (r.Country || r.country || "").trim();
if (landset.has(l)) {
samletAbo.set(l, (samletAbo.get(l) || 0) + tilTall(r.subscribers));
samletViews.set(l, (samletViews.get(l) || 0) + tilTall(r["video views"]));
antall.set(l, (antall.get(l) || 0) + 1);
}
}
const result = {};
for (const l of land) {
const n = antall.get(l) || 1;
result[l] = { abo: samletAbo.get(l) / n, views: samletViews.get(l) / n };
}
return result;
}
function main() {
const data = lesData();
const topp = toppTiLand(data);
console.log("\nTopp 10 land etter antall YouTube-kanaler:");
topp.forEach(([land, n], i) => console.log(`${(i+1).toString().padEnd(4)}${land.padEnd(20)}${n}`));
const snitt = snittPerLand(data, topp.map(([l]) => l));
console.log("\nGjennomsnitt per kanal i topp 10:");
topp.forEach(([land]) => {
const {abo, views} = snitt[land];
console.log(`${land.padEnd(20)}${abo.toLocaleString("nb-NO", {maximumFractionDigits:0}).padStart(15)} abonnenter${views.toLocaleString("nb-NO", {maximumFractionDigits:0}).padStart(20)} visninger`);
});
}
main();
En naturlig OO-modell består av en abstrakt Spillobjekt-klasse med posisjon og størrelse, og fire spesialiseringer: Menneske (styres av piltaster), Spokelse (beveger seg konstant, reflekteres ved kanter), Hindring (statisk, blokkerer Menneske), og Sau (statisk inntil bæres). I tillegg har vi en Spill-klasse som holder rede på alle objekter, oppdager kollisjoner, og kjører hovedløkken.
<!DOCTYPE html>
<html lang="no">
<head><meta charset="utf-8"><title>Manic Mansion</title></head>
<body style="text-align:center;font-family:sans-serif;background:#222;color:#eee">
<h2>Manic Mansion</h2>
<canvas id="lerret" width="800" height="600" tabindex="0" style="background:#e8d7b9;outline:none"></canvas>
<p id="info">Bruk piltaster eller WASD for å bevege deg.</p>
<script>
const BREDDE = 800, HOEYDE = 600, FRISONE = 80, FPS = 60;
const MENNESKE_FART = 4, SAU_BREMS = 0.5, SPOKELSE_FART = 3;
class Spillobjekt {
constructor(x, y, storrelse, farge) {
this.x = x; this.y = y; this.storrelse = storrelse; this.farge = farge;
}
rect() {
const s = this.storrelse;
return { x: this.x - s/2, y: this.y - s/2, w: s, h: s };
}
tegn(ctx) {
ctx.fillStyle = this.farge;
const r = this.rect();
ctx.fillRect(r.x, r.y, r.w, r.h);
}
}
function kolliderer(a, b) {
const ar = a.rect(), br = b.rect();
return ar.x < br.x + br.w && ar.x + ar.w > br.x && ar.y < br.y + br.h && ar.y + ar.h > br.y;
}
class Menneske extends Spillobjekt {
constructor(x, y) {
super(x, y, 28, "#1a2b4a");
this.bærerSau = false;
this.poeng = 0;
}
fart() { return MENNESKE_FART * (this.bærerSau ? SAU_BREMS : 1); }
oppdater(taster, hindringer) {
const f = this.fart();
let nx = this.x, ny = this.y;
if (taster.has("ArrowLeft") || taster.has("a")) nx -= f;
if (taster.has("ArrowRight") || taster.has("d")) nx += f;
if (taster.has("ArrowUp") || taster.has("w")) ny -= f;
if (taster.has("ArrowDown") || taster.has("s")) ny += f;
const s = this.storrelse / 2;
nx = Math.max(s, Math.min(BREDDE - s, nx));
ny = Math.max(s, Math.min(HOEYDE - s, ny));
// Aksevis kollisjonshåndtering
const forrigeX = this.x, forrigeY = this.y;
this.x = nx;
if (hindringer.some(h => kolliderer(this, h))) this.x = forrigeX;
this.y = ny;
if (hindringer.some(h => kolliderer(this, h))) this.y = forrigeY;
}
}
class Spokelse extends Spillobjekt {
constructor(x, y) {
super(x, y, 24, "#e57373");
const v = Math.random() * 2 * Math.PI;
this.dx = Math.cos(v) * SPOKELSE_FART;
this.dy = Math.sin(v) * SPOKELSE_FART;
}
oppdater() {
this.x += this.dx; this.y += this.dy;
const s = this.storrelse / 2;
if (this.x < s || this.x > BREDDE - s) this.dx = -this.dx;
if (this.y < s || this.y > HOEYDE - s) this.dy = -this.dy;
// Hold spøkelser ute av frisonene
if (this.x < FRISONE + s) this.dx = Math.abs(this.dx);
if (this.x > BREDDE - FRISONE - s) this.dx = -Math.abs(this.dx);
}
}
class Hindring extends Spillobjekt { constructor(x, y) { super(x, y, 36, "#785038"); } }
class Sau extends Spillobjekt { constructor(x, y) { super(x, y, 22, "#f0f0e6"); } }
class Spill {
constructor() {
this.menneske = new Menneske(40, HOEYDE / 2);
this.spokelser = []; this.hindringer = []; this.sauer = [];
this.tapt = false;
this._nySpokelse(); this._nyHindring(); this._nyHindring(); this._nyHindring();
this._nySau(); this._nySau(); this._nySau();
}
_tilfeldigMidten() {
return [
FRISONE + 30 + Math.random() * (BREDDE - 2 * FRISONE - 60),
30 + Math.random() * (HOEYDE - 60)
];
}
_nySpokelse() { const [x, y] = this._tilfeldigMidten(); this.spokelser.push(new Spokelse(x, y)); }
_nyHindring() { const [x, y] = this._tilfeldigMidten(); this.hindringer.push(new Hindring(x, y)); }
_nySau() {
const x = BREDDE - FRISONE + 10 + Math.random() * (FRISONE - 30);
const y = 20 + Math.random() * (HOEYDE - 40);
this.sauer.push(new Sau(x, y));
}
oppdater(taster) {
if (this.tapt) return;
this.menneske.oppdater(taster, this.hindringer);
this.spokelser.forEach(s => s.oppdater());
if (this.spokelser.some(s => kolliderer(this.menneske, s))) { this.tapt = true; return; }
if (!this.menneske.bærerSau) {
const idx = this.sauer.findIndex(s => kolliderer(this.menneske, s));
if (idx !== -1) {
this.sauer.splice(idx, 1);
this.menneske.bærerSau = true;
}
} else {
if (this.menneske.x < FRISONE) {
this.menneske.bærerSau = false;
this.menneske.poeng++;
this._nySau(); this._nySpokelse(); this._nyHindring();
} else if (this.sauer.some(s => kolliderer(this.menneske, s))) {
this.tapt = true;
}
}
}
tegn(ctx) {
ctx.fillStyle = "#e8d7b9"; ctx.fillRect(0, 0, BREDDE, HOEYDE);
ctx.fillStyle = "#c8e0c8";
ctx.fillRect(0, 0, FRISONE, HOEYDE);
ctx.fillRect(BREDDE - FRISONE, 0, FRISONE, HOEYDE);
[...this.hindringer, ...this.sauer, ...this.spokelser, this.menneske].forEach(o => o.tegn(ctx));
ctx.fillStyle = "#000"; ctx.font = "20px sans-serif";
ctx.fillText(`Poeng: ${this.menneske.poeng}${this.tapt ? " — TAPT" : ""}`, 10, 25);
}
}
const spill = new Spill();
const ctx = document.getElementById("lerret").getContext("2d");
const taster = new Set();
window.addEventListener("keydown", e => taster.add(e.key));
window.addEventListener("keyup", e => taster.delete(e.key));
document.getElementById("lerret").focus();
function loop() {
spill.oppdater(taster);
spill.tegn(ctx);
requestAnimationFrame(loop);
}
loop();
</script></body></html>
Spillobjekt og overstyrer oppdater()/tegn() ved behov.123456/
├── oppgave5/billettpris_flytdiagram.pdf
├── oppgave7c/nest_storst.mjs
├── oppgave11/
│ ├── oppgave11.mjs
│ └── youtube.csv
├── oppgave12/manic_mansion.html
└── README.md