Eksamen
25. november 2024 |
4 timer |
Lukket digital eksamen (med safe exam browser). |
Tillatte hjelpemiddel: bøker fra litteraturlisten og opp til 6 tosidige A4 ark med egne notat. |
- Bokmål: oppgavesett (vedlegg: for oppgaver 2b, 3b og 3c)
- Nynorsk: oppgavesett (vedlegg: for oppgaver 2b, 3b og 3c)
- Kursnotater er tilgjengelige og søkbare under eksamen: pdf
Oppgavetekster, løsningsforslag og sensorveiledning finner du på denne siden.
Fordi eksamen var en lukket digital eksamen uten tilgang til å kjøre koden eller bruke internett, bes sensor ikke gi poengtrekk for forhold som enkelt ville blitt oppdaget og raskt rettet ved kjøring av koden. Dette inkluderer blant annet:
- manglende
import
-setninger, - Feilstavede kodeord eller syntaks inspirert av andre programmeringsspråk (for eksempel hvis kandidaten skriver
fun
i stedet fordef
ved funksjonsdefinisjoner), - feil navn på funksjoner ved funksjonskall til egne funksjoner, innbygde funksjoner eller til funksjoner fra importerte moduler (såfremt det fremgår av funksjonsnavnet hva kandidaten egentlig mener – for eksempel hvis studenten har definert en funksjon som heter
rise_up
men kaller en funksjon som heterstand_up
blir det ikke trekk for dette), - feil navn på variabler (f. eks. kalle den samme variabelen både
total
ogsum
i ulike deler av koden), - enkle syntaks-feil (f. eks. manglende kolon etter
if
-setninger), - og så videre.
Logiske feil skal det likevel bli trukket for; selv om man kunne ha oppdaget at noe var feil ved kjøring av koden. Dette inkluderer blant annet:
-
presedensfeil,
-
forveksling av indekser og elementer,
-
off-by-one -feil,
-
setninger plassert med feil innrykk,
-
feil grunnet for tidlig return,
-
feil i algoritmer,
-
og så videre.
1 Automatisk rettet
- For å lese oppgavene uten fasit, se oppgavesettet over.
- Fasit for automatisk rettede oppgaver: bokmål, nynorsk
x = 42
y = 1
p = 3.14
s = '42'
li = ['42', (4, 5, 6, 7), None]
d = {
p: [s, li],
'x': y,
y: p,
}
Anta at kodesnutten over har blitt kjørt. Hva skrives ut i disse setningene? Dersom programmet ville krasjet på linjen, skriv kun Error.
PS: Husk at “hermetegn” og ‘apostrofer’ rundt strenger ikke blir med i utskriften.
print('s') | |
print(d[x]) | |
print(s[0]) | |
print(d[1]) | |
print(s * 2) | |
print(len(li)) | |
print(p < x) | |
print(d[y]) | |
print(d[p, 1]) | |
print(d[3.14][1][-1]) |
Klikk på de grå feltene for å se svaret.
x = 42
y = 1
p = 3.14
s = '42'
li = ['42', (4, 5, 6, 7), None]
d = {
p: [s, li],
'x': y,
y: p,
}
expressions = '''\
print('s')
print(d[x])
print(s[0])
print(d[1])
print(s * 2)
print(len(li))
print(p < x)
print(d[y])
print(d[p, 1])
print(d[3.14][1][-1])
'''
for expression in expressions.splitlines():
print(f'{expression:24}--> ', end='')
try:
eval(expression)
except Exception:
print('Error')
For å se fasit, klikk «kjør».
a = 1
b = 2
a = a + b
b = a * b
a -= 1
print(b - a)
Hva skriver dette programmet ut? (hvis programmet krasjer, skriv kun Error)
For å se fasit, klikk «kjør».
def decremented(x):
return x - 1
def foo(x):
x += 2
return x + decremented(x)
x = 3
x = foo(x)
print(x)
Hva skriver dette programmet ut? (hvis programmet krasjer, skriv kun Error)
For å se fasit, klikk «kjør».
s = 'abc'
t = ''
for c in s:
t = c + t
print(t)
Hva skriver dette programmet ut? (hvis programmet krasjer, skriv kun Error)
For å se fasit, klikk «kjør».
def f(d):
for k in d:
c = 0
if d[k] == 0:
c = 1
d[k] = c
d = {
'a': 0,
'b': 1,
'c': 2,
'd': 0,
'e': 1,
}
r = f(d)
Anta at kodesnutten over har blitt kjørt. Hva skrives ut i disse setningene? Dersom programmet ville krasjet på linjen, skriv kun Error.
print(r) | |
print(d['a']) | |
print(d['b']) | |
print(d['c']) | |
print(d['d']['e']) |
Klikk på de grå feltene for å se svaret.
def f(d):
for k in d:
c = 0
if d[k] == 0:
c = 1
d[k] = c
d = {
'a': 0,
'b': 1,
'c': 2,
'd': 0,
'e': 1,
}
r = f(d)
expressions = '''\
print(r)
print(d['a'])
print(d['b'])
print(d['c'])
print(d['d']['e'])
'''
for expression in expressions.splitlines():
print(f'{expression:24}--> ', end='')
try:
eval(expression)
except Exception:
print('Error')
For å se fasit, klikk «kjør».
t = ['a', 'b', 'c']
for i in range(len(t)):
m = t[i]
t[i] = t[i-1]
t[i-1] = m
print(t)
Hvilken verdi har t
etter at kodesnutten har kjørt?
For å se fasit, klikk «kjør».
2 Forklaring
def foo(bar):
ham = bar[0]
for egg in range(1, len(bar)):
glass = bar[egg]
if ham >= glass:
return False
ham = glass
return True
Hva slags funksjon er dette?
- Forklar hva funksjonen gjør.
- Koden over har dårlige variabel- og funksjonsnavn, som gjør den vanskelig å lese og forstå. Forklar rollen til de ulike variablene i koden over, og foreslå nye, selvbeskrivende navn for variabel- og funksjonsnavn.
PS: svar gjerne med en tabell (kopier og fyll ut):
Org. navn | Bedre navn | Hva/hvorfor/hensikt/forklaring |
---|---|---|
foo | … | … |
bar | … | … |
ham | … | … |
egg | … | … |
glass | … | … |
Maksimalt 600 ord.
Org. navn | Bedre navn | Hva/hvorfor/hensikt/forklaring |
foo | is_strictly_increasing | Funksjonen avgjør om elementene i en liste eller annen ordnet samling kommer i (strengt) stigende rekkfølge eller ikke. Dersom elementene i samlingen alle er ulike og kommer i stigende rekkefølge, returnerer funksjonen True; hvis ikke returnerer den False. |
bar | elements | Parameter/input til funksjonen. Vi ser at dette sannynligvis er en liste, tuple eller streng, siden den har en lengde og vi indekserer den med heltall mellom 0 og objektets strørrelse. |
ham | previous_element | En lokal variabel som alltid peker på forrige element i listen når en iterasjon av løkken begynner. I første iterasjon av løkken viser denne variabelen til det første elementet i samlingen (på indeks 0), mens iteranden i samme iterasjon er 1. Helt på slutten av løkken oppdateres variabelen til å peke på nåværende element, slik at når neste iterasjon begynner like etterpå vil variabelen igjen peke på forrige element i listen. |
egg | i | Iteranden i løkken. Den er i vårt tilfelle en indeks, siden løkken går gjennom en range opp til lengden av elementsamlingen, og variabelen brukes til å indeksere elementsamlingen. Konvensjon tilsier da at i er et godt variabelnavn. |
glass | current_element | Variabel som representerer elementet som befinner seg på indeks i gjeldende iterasjon av løkken. |
def is_strictly_increasing(elements):
previous_element = elements[0]
for i in range(1, len(elements)):
current_element = elements[i]
if previous_element >= current_element:
return False
previous_element = current_element
return True
Demonstrert forståelse (5 poeng):
- Argumentet til funksjonen er forventet å være en samling av ting
- Iteranden i løkken representerer en indeks
- Variablene «ham» og «glass» representerer ulike elementer i samlingen
- Funksjonen sammenligner elementer som ligger rett etter hverandre i ordningen. Dersom kandidaten på et eller annet vis kommuniserer at funksjonen sjekker om listen er sortert, skal dette punktet og punktene over alle regnes som forstått.
- Funksjonen returnerer True hvis hele samlingen av ting er strengt sortert (altså er sortert og alle elementene er ulike).
Fornuftige variabelnavn (5 poeng):
foo
: strictly_increasing, is_increasing, alphabetic, is_sorted osv er alle fullt godkjente.bar
: elements, things, values, numbers, num_list, the_list, array, arr, words, names, strings, items, data_list og så videre er alle godkjente. Det er også godkjent med kun «a» med henvisning til konvensjon. Det er derimot ikke godkjent å bruke «list» som variabelnavn; selv om dette forsåvidt gir full uttelling for forståelse, er «list» navnet på en klasse i Python, og brukes f. eks. når man konverterer til liste (list('abc') == ['a', 'b', 'c']
) eller for å opprette en tom liste (list()
)egg
: i tillegg til «i» er også forslag som index og position godkjente. Ikke godkjent er forslag som iterand, element, value og lignende.ham
: previous, prev_element, early_item, old_value, eller lignende. Noe som på en eller annen måte indikerer at det er et element som har en tidligere plass i ordningen.glass
: value, current, this_element, later_item, new_value eller lignende. Noe som på en eller annen måte indikerer at det er et element i samlingen.
Poeng for «ham» og «glass» må sees i sammenheng; ett poeng hvis minst ett av navnene indikerer at dette er elementer i samlingen, to poeng hvis navngivningen til sammen indikerer rekkefølge på elementene.
Norske variabelnavn er OK så lenge man er konsekvent (ved blanding av norske og engelske variabelnavn scores besvarelsen som normalt, men maks poeng er 9/10).
Sensor kan gjøre en skjønnsmessig justering dersom kandidaten på andre måter demonstrerer vesentlig mer eller mindre forståelse enn hva rubrikken legger opp til.
Et gammelt jungelord sier at programmering består av 10% koding og 90% å forstå hvorfor det ikke virker. I denne oppgaven fokuserer vi på de siste 90%.
Du har nettopp slengt sammen et Asteroids-spill med god hjelp av kunstig intelligens. Spillet ser ut til å virke som det skal helt til en kule treffer en stein. Da krasjer spillet med denne feilmeldingen:

Traceback (most recent call last):
File "/path/to/asteroids.py", line 55, in timer_fired
check_bullet_hits(app)
File "/path/to/asteroids.py", line 132, in check_bullet_hits
x, y, radius, _, _ = app.stones[j]
~~~~~~~~~~^^^
IndexError: list index out of range
Oppførselen vi beskriver over skjer de fleste ganger, men ikke alltid: av og til klarer skuddet faktisk å skyte bort steinen.
Kildekoden for spillet ligger i vedlagt pdf.
Forklar:
- Hva betyr feilmeldingen? Forklar all informasjonen feilmeldingen gir oss.
- Hva er grunnen til at feilen oppstår? Hvorfor krasjer ikke spillet hver eneste gang en kule treffer en stein?
- Hvilke mulige løsninger ser du for deg? Du skal ikke skrive kode, men forklare med ord og relevante fagbegreper hva som er mulige løsninger og hvordan de vil løse problemet.
Hva betyr feilmeldingen? Forklar all informasjonen feilmeldingen gir oss.
- Feilmeldingen forteller at indeksen
j
er for stor eller for liten («out of range»), slik at det ikke finnes noe element i denne posisjonen i app.stones. - Vi ser også at app.stones er en liste («list index out of range»).
- Krasjen skjer på linje 132, som er en del av funksjonen check_bullet_hits i filen asteroids.py.
- Funksjonen check_bullet_hits ble kalt på linje 55, som er en del av funksjonen timer_fired i filen asteroids.py.
Hva er grunnen til at feilen oppstår?
Listen app.stones blir kortere underveis i for-løkken hvor j
er iteranden (.pop fjerner jo et element fra listen). Hvilke verdier løkken skal iterere gjennom ble derimot bestemt før løkken egentlig startet, nemlig da uttrykket range(len(app.stones))
ble evaluert. Når løkken kommer til siste iterasjon viser iteranden j
altså til siste indeks i app.stones slik den var da løkken startet. Men fordi den har krympet underveis, er indeksen for stor, og programmet krasjer.
(forøvrig har koden akkurat samme bug når det gjelder app.bullets
og løkken med iteranden i
)
Hvorfor krasjer ikke spillet hver eneste gang en kule treffer en stein?
Programmet vil ikke krasje her dersom steinen som treffes er den siste steinen i listen.
Hvilke mulige løsninger ser du for deg?
Jeg ser for meg flere mulige løsninger:
-
Returner fra funksjonen med en gang vi finner en stein truffet av en kule. Funksjonen vil riktignok kun fjerne én stein og én kule, men siden funksjonen kalles veldig ofte er det ikke så lenge til neste gang den kalles at det vil være noe problem i praksis. I teorien vil vi kunne få situasjoner der en kule flyr gjennom en stein uten å ødelegge den, så den er ikke helt idéell.
-
Kall en hjelpefunksjon som fjerner første funnede treff i en while-løkke. Dette er i grunnen den samme løsningen som nummer (1), men vi lar funksjonen returnere True hvis den fant en treff og fjernet stein/kule og False hvis ikke. Så bruker vi den som en hjelpefunksjon, og kaller på den i en while-løkke helt til den returnerer False.
-
Bruk break for å avbryte løkken når en stein er truffet. Det er en rimelig antakelse at én kule kun treffer én stein – så når vi har funnet en truffet stein er det strengt tatt ikke behov for flere iterasjoner av den indre løkken som går gjennom steinene, og vi kan bruke break for å avslutte løkken tidlig. Denne løsningen gjør forsåvidt at problemet med krasj på linje 132 forsvinner, men løser ikke det tilsvarende problemet som er gjeldende for
app.bullets
ogi
i den ytterste løkken. -
Legg steiner og kuler som skal fjernes i egen liste først. Vi bruker først en løkke lignende den vi allerede har for å identifisere hva som skal fjernes av steiner og kuler, men i stedet for å fjerne («poppe»), legger vi istedet det som skal fjernes i egne lister over «markerte» steiner/kuler. Deretter har vi ekstra løkker etterpå for å fjerne de markerte steinene/kulene.
-
Opprett nye lister over kuler og steiner i stedet for å mutere eksisterende liste. Vi kan først opprette en ny liste som inneholder alle steiner som ikke er truffet av noen kule, og tilsvarende en ny liste av alle kuler som ikke er truffet av noen stein, og så la app.bullets og app.stones vise til disse nye listene. Det er i så fall aktuelt med hjelpefunksjoner
stone_is_hit(single_stone, list_of_bullets)
ogbullet_hit_something(single_bullet, list_of_stones)
for å avgjøre hvilke kuler og steiner vi skal ta vare på. Alternativt kan vi også gjøre som i løsningsforslag (4) først, og deretter lage de nye listene over kuler og steiner med de elementene som ikke er markert. -
Gå gjennom indeksene baklengs. I stedet for at løkkene begynner med den første indeksen og beveger seg oppover, kan vi i stedet begynne med den høyeste indeksen og gå nedover. Da får vi ikke problemer når listen blir kortere, siden elementene som får nye indekser etter popping er de elementene som allerede er ferdig sjekket. For å gjøre dette kan vi bruke en range med negative steg, f. eks.
range(len(app.stones) - 1, -1, -1)
i stedet forrange(len(app.stones))
ogrange(len(app.bullets) - 1, -1, -1)
i stedet forrange(len(app.stones))
. En annen variant av dette er å bruke negative indekser når vi henter ut elementer, dvs brukeapp.stones[-j-1]
i stedet forapp.stones[j]
ogapp.stones.pop(-j-1)
i stedet forapp.stones.pop(j)
(og tilsvarende fori
ogapp.bullets
). -
Bruk while-løkker. Ved å bruke while-løkker i stedet for for-løkker har vi større kontroll på hvordan indeksen endrer seg fra iterasjon til iterasjon. Betingelsen i while-løkker (typisk
i < len(app.bullets)
ogj < len(app.stones)
) sjekkes dessuten på nytt i hver iterasjon, slik at hvis lengden på listene minker underveis, vil løkken også gjøre færre iterasjoner. Det kan imidlertid oppstå feil ved at noen sjekker «hoppes over» om vi ikke er forsiktig med hvordan indeksene økes: når vi fjerner noe fra en liste, vil resten av listen flytte seg nedover, så å øke indeksen vil hoppe over elementet som ble flyttet ned. Vi kan ta høyde for dette ved å kun inkrementere indeksene dersom vi ikke fjernet. Uansett om vi ikke tar høyde for dette, vil feilen være av begrenset betydning i praksis, siden funksjonen kalles på nytt mange ganger i sekundet.
Hva betyr feilmeldingen? (4 poeng)
- Listen er for liten for indeksen som benyttes (2 poeng).
- Identifiserer at krasjen skjer på linje 132, eller identifiserer at feilen skjer som en del av et funksjonskall til check_bullet_hits (1 poeng).
- Identifiserer at funksjonskallet til check_bullet_hits ble gjort på linje 55, eller at det ble gjort som en del av timer_fired (1 poeng).
Hva er grunnen til at feilen oppstår? Hvorfor krasjer ikke spillet hver eneste gang en kule treffer en stein? (3 poeng)
- Listen blir kortere underveis i løkken (2 poeng).
- Programmet vil ikke krasje her dersom steinen som treffes er den siste steinen i listen (1 poeng).
Hvilke mulige løsninger ser du for deg? (4 + 1 poeng)
- Et løsningsforslag som ikke fungerer gir 0-1 poeng basert på sensors skjønn.
- Et løsningsforslag som vil fungere greit i praksis, men som i teorien har noen svakheter (f. eks. å returnere fra funksjonen etter første treff) gir 2 poeng hvis det presenteres uten å påpeke svakhetene, men gir 3 poeng hvis svakhetene drøftes.
- Et fullverdig løsningsidé gir 4 poeng. Løsningen trenger ikke beskrives i pinlig detalj, så lenge idéen er kommunisert (for eksempel er det tilstrekkelig å si at vi kan gå gjennom listene baklengs i stedet for fremlengs uten å gå i detalj om hvordan dette gjøres.)
I tillegg
- Løsningsforslaget tar hensyn til funksjonen som helhet og retter også opp identisk bug/den delen av bug’en som er knyttet til app.bullets og ytterste løkke. Poenget deles ut så lenge kandidaten viser forståelse for at samme bug gjelder for app.bullets og ytre løkke, eller om løsningsforslaget implisitt tar slike hensyn. (1 poeng).
Helhetsvurdering (3 poeng). Poengene i denne kategorien deles ut helhetlig, ikke punkt for punkt. Sensor kan bruke dennne kategorien for å trekke opp eller ned kandidater som demonstrerer større eller mindre forståelse enn hva rubrikken forøvrig tilsier. Sensor ser etter:
- Det er lett å forstå hva kandidaten mener.
- God/relevant bruk av faguuttrykk.
- Demonstrerer forståelse av koden og mulighetene for endring.
3 Kodeskriving

Skriv en kodesnutt slik at minnets tilstand blir som vist over. Du vil ikke få trekk i poeng dersom du definerer andre variabler i tilegg.
Klikk på «se steg» -knappen for å verifisere at denne koden gir riktig bilde av minnet.
a = [1, 2, 3]
b = a
c = [[1, 2, 3], a]
temp1 = [1, 2, 3]
temp2 = [1, 2, 3]
a = temp1
b = temp1
c = [temp2, temp1]
c = [[1, 2, 3], [1, 2, 3]]
a = c[1]
b = c[1]
a = b = [1, 2, 3]
c = [[1, 2, 3], a]
- 1 poeng hvis a og b begge er like
[1, 2, 3]
og c er lik[[1, 2, 3], [1, 2, 3]]
- 1 poeng hvis
a
ogb
er aliaser - 1 poeng hvis
c[1]
er alias meda
ogb
, mensc[0]
ikke er alias med hverkena
ellerb
.
Sensor har anledning til å gjøre en skjønnsmessig justering i tilfeller der det er gjort feil disse testene ikke tar høyde for. Poengsummen som beskrevet over kan regnes ut med følgende kode:
points = sum([
a == b == [1, 2, 3] and c == [[1, 2, 3], [1, 2, 3]],
a is b,
c[1] is a is b and a is not c[0] is not b,
])
# PS: i kontekst av matematisk aritmetikk regnes True som 1 og False som 0.
I denne oppgaven skal vi mutere oppslagsverk der noen av nøklene begynner med 'lab'
mens andre ikke gjør det. For eksempel:
{
'student_name': 'Kari',
'lab1': 20,
'lab2': 22,
'lab3': 23,
}
Vi vet ikke hvor mange nøkler det er eller hvor mange av dem som begynner med ’lab’, men vi vet at for alle nøkler som begynner med ’lab’ skal verdien være en tallverdi.
Skriv funksjon add_total_lab_score
med en parameter data for et oppslagsverk på formen beskrevet over. Funksjonen skal regne ut summen av alle verdiene knyttet til nøkler som begynner med ’lab’. Deretter skal funksjonen mutere oppslagsverket ved å legge til nøkkel 'total_lab_score'
som viser verdien som ble funnet.
Testen under dokumenterer hvordan vi forventer at funksjonen skal fungere:
def test_add_total_lab_score():
print('Testing add_total_lab_score...', end='')
# Test A
data = {
'student_name': 'Kari',
'lab1': 20,
'lab2': 22,
'lab3': 23,
}
add_total_lab_score(data)
assert data == {
'student_name': 'Kari',
'lab1': 20,
'lab2': 22,
'lab3': 23,
'total_lab_score': 65,
}
# Test B
data = {
'course_id': 'FNI100',
'labA': 25,
'labB': 25,
}
add_total_lab_score(data)
assert data == {
'course_id': 'FNI100',
'labA': 25,
'labB': 25,
'total_lab_score': 50,
}
print(' OK')
def add_total_lab_score(data):
total_score = 0
for key in data:
if key[:3] == 'lab':
value = data[key]
total_score += value
data['total_lab_score'] = total_score
- Funksjonsdefinisjonen har én parameter (1 poeng)
- Løkke gjennom oppslagsverket (2 poeng)
- Korrekt forståelse av hva som er nøkkel og hva som er verdi i løkken (2 poeng)
- Fornuftig sjekk om en streng begynner med ’lab’, her godtar vi både startswith og beskjæring (og også full pott for feil navngitte versjoner av dette, for eksempel «beginswith», eller feilaktig syntaks men som likevel demonstrerer forståelse av hva som er hva, for eksempel
my_key(0,3)
som et forsøk på beskjæring). Mislykkede forsøk på en slik sjekk (f. eks.if 'lab' == noe_helt_feil
) men som likevel er bra plassert i forhold til løkken gir 50% uttelling på dette punktet. (2 poeng) - Fornuftig akkummulering av resultatat i en samlevariabel (2 poeng).
- Legge til ny nøkkel i oppslagsverket med opptelt verdi. Det trenger ikke være syntaktisk korrekt så lenge plassering er fornuftig og intensjonen er tydelig. (1 poeng)
Dersom en besvarelse er betydelig bedre eller dårligere enn rubrikken tilsier, har sensor anledning til å justere poengsummen etter eget skjønn.
I en fjern galakse langt langt unna var det en gang en emneadministrator Luke Astrix for emnet FNI100. Studentene skulle få karakter delvis basert på laber de hadde gjort underveis i emnet og delvis basert på eksamen.
-
Eksamen ble anonymt rettet av en ekstern sensor på en skala fra 0-80. Den eneste informasjonen sensor hadde om hvilken kandidat de rettet, var et kandidatnummer. I exam_scores.csv kan du se et eksempel på hva slags fil sensoreren vil levere fra seg til Luke Astrix når rettingen er ferdig.
-
Hvilken student som skjuler seg bak et kandidatnummer har Luke Astrix allerede gjort klar i en egen CSV-fil. Du kan se et eksempel på en slik fil i candidate_numbers.csv (men selvfølgelig; dette er hemmelig informasjon, så vi viser deg bare et dummy-eksempel).
-
I løpet av semesteret har studentene også fått poeng på laber de har levert inn. Score på labene samt annen informasjon om studentene finner du i en fil som ser ut som student_overview.csv.
Siden Luke Asterix ikke har gjennomført FNI100 selv, har du fått et viktig oppdrag:
- Skriv et Python-program som, når det kjøres, lager en ny fil student_overview_with_exam_score.csv med samme data som student_overview.csv men med en ekstra kolonne: poengene studenten fikk på eksamen.
Du kan anta at det alltid er ett kandidatnummer for hver student, og én eksamensscore for hvert kandidatnummer
exam_scores.csvcandidate_number;exam_score 100;78 105;70 107;45 101;32candidate_numbers.csv
candidate_number;student_name 100;Rey 101;Ahsoka 105;Finn 107;Kylostudent_overview.csv
student_name;lab1;lab2;lab3;lab4 Kylo;25;20;23;22 Rey;20;22;25;24 Finn;22;23;24;25 Ahsoka;20;2;1;0Forventet resultat i student_overview_with_exam_scores.csv (legg merke til den ekstra kolonnen):
student_name;lab1;lab2;lab3;lab4;exam_score Kylo;25;20;23;22;45 Rey;20;22;25;24;78 Finn;22;23;24;25;70 Ahsoka;20;2;1;0;32
Tips til hjelpefunksjoner som kan være nyttig å skrive:
- Hjelpefunksjon for å lese en semikolon-separert CSV-fil.
- Hjelpefunksjon for å skrive en semikolon-separert CSV-fil.
- Hjelpefunksjon som returnerer kandidatnummeret for en gitt student.
- og så videre...
import csv
import io
from pathlib import Path
def read_csv(path):
'''
Read a CSV file and return the headers and data as a tuple.
The data is a list of dictionaries where each dictionary
represents a row. The file is read with ';' as delimiter.
'''
content = Path(path).read_text(encoding='utf-8')
reader = csv.DictReader(io.StringIO(content), delimiter=';')
return reader.fieldnames, list(reader)
def write_csv(path, headers, data):
'''
Write a CSV file with the given headers and data.
The data should be a list of dictionaries where each dictionary
represents a row. The file is written with ';' as delimiter.
'''
result = io.StringIO()
writer = csv.DictWriter(result, fieldnames=headers, delimiter=';')
writer.writeheader()
writer.writerows(data)
Path(path).write_text(result.getvalue(), encoding='utf-8')
def search_for_row(data, column, target_value):
'''
Return the first row in the data where the value in the
given column matches the target value. If no row is found,
return None.
'''
for row in data:
if row[column] == target_value:
return row
return None
def get_candidate_number(candidates_data, student_name):
'''
Return the candidate number for the given student name.
The candidates_data must be a list of dictionaries representing
a CSV file with the columns 'student_name' and 'candidate_number'.
'''
row = search_for_row(candidates_data, 'student_name', student_name)
return row['candidate_number']
def get_exam_score(exams_data, candidate_number):
'''
Return the exam score for the given candidate number.
The exam_data must be a list of dictionaries representing
a CSV file with the columns 'candidate_number' and 'exam_score
'''
row = search_for_row(exams_data, 'candidate_number', candidate_number)
return row['exam_score']
def add_exam_score(org_path, dest_path, candidates_path, exam_scores_path):
'''
Add the exam score to the original data and write the result to the
destination path. The original data in org_path must be a CSV file
with the column 'student_name'. The file at candidates_path must
be a CSV file with the columns 'student_name' and 'candidate_number'.
The file at exam_scores_path must be a CSV file with the columns
'candidate_number' and 'exam_score'.
'''
org_headers, main_data = read_csv(org_path)
_, candidates_data = read_csv(candidates_path)
_, exam_scores_data = read_csv(exam_scores_path)
new_headers = org_headers + ['exam_score']
for row in main_data:
student_id = row['student_name']
candidate_number = get_candidate_number(candidates_data, student_id)
exam_score = get_exam_score(exam_scores_data, candidate_number)
row['exam_score'] = exam_score
write_csv(dest_path, new_headers, main_data)
if __name__ == '__main__':
add_exam_score(
org_path='csv_combine/student_lab_scores.csv',
dest_path='csv_combine/total_lab_exam_score.csv',
candidates_path='csv_combine/candidate_numbers.csv',
exam_scores_path='csv_combine/exam_scores.csv'
)
Rubrikken her tar for seg ulike elementer av eksempelløsningen. Dersom kandidaten har brukt en radikalt annerledes fremgangsmetode og andre datastrukturer vil sensor bruke skjønn for å score oppgaven så godt som mulig.
Lese og skrive til CSV (4 poeng). For denne delen av oppgaven holder det å finne riktig sted i kursnotatene og kopiere derfra, kanskje gjøre noen tilpasninger for å bruke riktig skilletegn. Man får uttelling også for å bruke andre metoder. Det er ikke et krav at man representerer data som liste av oppslagsverk, det er også fullt mulig å bruke 2D-lister.
- Leser CSV-filen (2 poeng)
- Skriver til CSV-filen (2 poeng)
Søk etter rad for å hente informasjon (4 poeng). Vi tenker her på prosessesen der man henter informasjon om kandidatnummer gitt at du vet navnet på studenten, og der man henter informasjon om eksamensscore gitt at man vet et kandidatnummmer. Disse to stegene utføres på samme måte: en løkke gjennom hver rad, sammenligning av verdien i én kolonne opp hva man søker etter, og uthenting av verdien i en annen kolonne i samme rad man fant søketreff.
- Løkke gjennom radene i tabellen for kandidatnummer (1 poeng)
- Løkke gjennom radene i tabellen for eksamensscore (1 poeng)
- Fornuftig søkekriterie/betingelse i søk for studentnavn og fornuftig uthenting av kandidatnummer ved søketreff (1 poeng)
- Fornuftig søkekriterie/betingelse i søk for kandidatnummer og fornuftig uthenting av eksamensscore ved søketreff (1 poeng)
Det er også mulig å gjennomføre pre-prosessesering hvor man for eksempel oppretter oppslagsverk hvor søkeordet er en nøkkel og verdien man ønsker seg er verdien. Dette er en fullgod løsning (den er også mer effektiv med tanke på kjøretid).
Sammenknytning (4 poeng). Vi tenker her på de store linjene i programmet, hvor man typisk bruker en løkke gjennom hovedtabellen og legger til kolonnen for eksamensscore for hver student.
- Løkke gjennom radene i hovedtabell (1 poeng)
- Gjør et forsøk på å finne eksamensscore for hver student ved å knytte sammen søk etter kandidatnummer og søk etter eksamensscore. (1 poeng)
- Lager til resultat-tabellen, enten ved å opprette helt nye rader for en ny tabell eller ved å mutere eksisterende rader slik at det blir data for en ny kolonne i hver rad. (1 poeng)
- Håndterer overskriftene i den nye tabellen (1 poeng)
Helhetsvurdering (3 poeng). Poengene i denne kategorien deles ut helhetlig, ikke punkt for punkt. Sensor kan bruke dennne kategorien for å trekke opp eller ned kandidater som demonstrerer større eller mindre forståelse enn hva rubrikken forøvrig tilsier. Sensor ser etter:
- Det er lett (i hvert fall ikke veldig vanskelig) å forstå hensikten med ulike deler av koden.
- Gode valg av datastrukturer (liste/oppslagsverk/etc og hva slags ting som er i dem) for å representere dataene i problemet.
- Koden er ikke unødvendig knotete eller komplisert.
- Kandidaten demonstrerer forståelse for hvordan problemløsningen kan deles inn i flere steg. Inndelingen i steg/delproblemer gir mening med tanke på hva som er mulig og fornuftig.