Eksamen
13. mai 2024 |
4 timer |
Lukket digital eksamen (med safe exam browser). |
Alle skrevne og trykte hjelpemidler tillatt. |
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, - manglende kodeord (som f. eks. manglende
def
foran funksjonsdefinisjoner), - feil navn på funksjoner og metoder i standardbiblioteket, i egen kode eller i eksterne moduler (såfremt det fremgår noenlunde av funksjonsnavnet hva kandidaten egentlig mener),
- 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 (som hovedregel) bli trukket litt 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,
-
feil i algoritmer,
-
og så videre.
1 Automatisk rettet
- For å lese oppgavene uten fasit, se oppgavesettet over.
- Fasit for automatisk rettede oppgaver: pdf
2 Forklaring
Alfred skal plotte en linje, og har skrevet et program over to Python-filer for å få det til. Når Alfred kjører my_script.py krasjer imidlertid programmet hans og viser en feilmelding i terminalen.
my_lineplotter.py
|
|
my_script.py
|
|
Traceback (most recent call last):
File "/path/to/my_script.py", line 6, in <module>
plot_line(line)
File "/path/to/my_lineplotter.py", line 7, in plot_line
x = point[0]
~~~~~^^^
TypeError: 'int' object is not subscriptable
Forklar:
- Hva betyr feilmeldingen? Forklar hvilken informasjonen feilmeldingen gir oss.
- Hvordan kan du rette programmet slik at det viser et plot som vist i illustrasjonen i stedet for å krasje? Forklar nøyaktig hvor og hvordan du planlegger å gjøre rettelsen.
Bruk vanlig språk; du skal ikke skrive store kodesnutter. Samtidig må du være detaljert og presis nok til å overbevise sensor om at du forstår hva du snakker om.
Maks 600 ord.
Feilmeldingen TypeError: 'int' object is not subscriptable
betyr at programmet forsøker å indeksere et heltall. Å indeksere er noe man vanligvis gjør med lister, tupler og strenger for å hente ut enkeltverdier på en gitt indeks/posisjon (det er også mulig å indeksere et oppslagsverk, siden en nøkkel i et oppslagsverk er en generalisert form for indeks). En int er derimot bare et enkelt tall, og det gir ikke mening å indeksere et enkelt tall.
Feilmeldingen forteller oss altså at variabelen point
er en int når vi forsøker å gjennomføre linje 7 i my_lineplotter.py. Vi ser også av feilmeldingen at dette er en linje i funksjonen plot_line. Feilmeldingen forteller oss dessuten at funksjonskallet til plot_line
ble gjort fra linje 6 i hovedprogrammet i my_script.py.
Ved inspeksjon av funksjonen plot_line ser vi at funksjonen forventer at line
er en liste av «punkter», hvor hvert punkt er en tuple (evt. liste) av to elementer (x, y). Ved inspeksjon av my_script.py ser vi derimot at argumentet til plot_line er en liste av heltall, og ikke en liste av tupler. Feilen oppstår altså fordi koden i my_lineplotter.py og my_script.py ikke er enig med hverandre om hvilket format line
skal ha; my_lineplotter.py forventer en liste av tupler, mens my_script.py gir en liste av heltall hvor annethvert tall er x-koordinat og det neste er y-koordinat.
Feilen kan rettes på én av to måter:
-
Endre
line
i my_script.py til å være en liste av tupler, slik at argumentet til plot_line får riktig format. Dette kan gjøres ved å endre linje 6 i my_script.py tilline = [(0, 0), (1, 2), (2, 1), (3, 3)]
. Dette er den enkleste løsningen, og den som er mest i tråd med hvordan funksjonen plot_line er skrevet. Ingen endringer behøves i my_lineplotter.py. -
Endre
plot_line
i my_lineplotter.py til å håndtere en liste av heltall hvor annethvert tall skal legges til i xs og i ys. En slik endring gjør at vi må endre en vesentlig del av løkken i plot_line. Det er flere veier til mål; én mulighet er å benytte en løkke som itererer over indekser i stedet for elementer, og henter ut x og y ved å indeksereline
medline[i]
ogline[i+1]
i stedet forpoint[0]
ogpoint[1]
. En slik løkke må kun gå gjennom partallsindekser, dvs benytterange(0, len(line), 2)
som iterabel i for-løkken. Ingen endringer behøves i my_script.py.
Bokstavelig tolkning av feilmeldingen (3 poeng)
- Krasjen skjer på linje 7 i my_lineplotter.py (1 poeng)
- Krasjen skjer fordi
point
er en int (1 poeng) - plot_line ble kalt fra linje 6 i my_script.py (1 poeng)
Forståelse av feilen (5 poeng)
- Forståelse av hva indeksering er (2 poeng)
- Feilen skyldes at
line
i my_script.py er en liste av heltall, mensplot_line
forventer en liste av «punkter» representert som tupler eller lister (3 poeng)
Forslag til rettelse (3 poeng)
- Gir en presis forklaring på minst én mulig løsning (3 poeng)
Helhetsvurdering (3 poeng)
- Meningsfulle forklaringer av sammenhenger.
- God bruk av faguttrykk.
- Finner ikke feil som ikke er feil.
- Kategorien vurderes helhetlig.
def g(b, r):
s = 0
for e in b:
if e == r:
s += 1
return s
def f(a):
x = None
for y in a:
if x is None:
x = y
elif g(a, y) > g(a, x):
x = y
return x
Hva slags funksjoner er dette?
- Forklar hva funksjonene 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 funksjonsnavnene. Dersom du mener det ikke er nødvendig å endre et variabelnavn, forklar hvorfor variabelnavnet er egnet slik det allerede er.
8 poeng: forklar den øverste funksjonen, og gi bedre variabel- og funksjonsnavn til:
- g
- b
- r
- s
- e
8 poeng: forklar den nederste funksjonen, og gi bedre variabel- og funksjonsnavn til:
- f
- a
- x
- y
Maks 600 ord.
Den øverste funksjonen (g)
Denne funksjonen teller antall forekomster av et element r
i en liste b
(kan også være en annen type samling, f. eks. en streng eller tuple). Variabelen s
er en teller som økes med 1 for hver forekomst av r
i b
.
Forslag til nye navn | |
---|---|
g |
count_occurrences |
b |
collection |
r |
element_to_count |
s |
count |
e |
Dette variabelnavnet kan være egnet slik det er. Det er en vanlig konvensjon å bruke e som navn på iteranden i en for-løkke over elementer i en generell samling. Gode alternativer kan også være element , current_element , item etc. |
Den nederste funksjonen (f)
Denne funksjonen finner det elementet som forekommer flest ganger i en liste a
(kan også være en annen type samling, f. eks. en streng eller tuple). Funksjonen går gjennom alle elementene i a
, og oppdaterer x
til å hele tiden peke på det elementet vi har sett så langt som forekommer flest ganger. Variabelen y
er en midlertidig peker til et element i a
som sammenlignes med x
for å finne ut om y
forekommer flere ganger enn x
.
Forslag til nye navn | |
---|---|
f |
most_common_element |
a |
Dette variabelnavnet kan være egnet slik det er, da det er en vanlig konvensjon å bruke a som navn på en generell liste av elementer. Gode alternativer kan være collection , elements etc. |
x |
most_common_so_far |
y |
current_element |
Den øverste funksjonen (g)
- 2 poeng: forklaring/forståelse («teller antall forekomster av r i b»).
- 3 poeng: godt funksjonsnavn og gode parameternavn
- Eksempler på gode funksjonsnavn:
count_occurrences
,count_element
,count_element_in_collection
,number_of_occurrences
,count
, og lignende. - Eksempler på gode navn for b:
collection
,elements
,items
,chars
,word
,lst
,sequence
,data
,container
,objects
,entries
,values
,characters
,letters
,words
,iterable
, etc. Aksepterer ogsåa
. - Eksempler på gode navn for r:
element_to_count
,element
,item
,letter
,obj
,entry
,value
, etc.
- Eksempler på gode funksjonsnavn:
- 1 poeng: lokale variabelnavn
- Eksempler på gode navn for s:
count
,counter
,occurrences
,occurrence
,n
,total
,tally
,cnt
,occur
,num_occurrences
,num_occ
,num
,n_occurrences
, etc. Aksepterer ogsåresult
,res
og lignende. - Eksempler på gode navn for e:
element
,item
,entry
,value
,obj
,letter
, eller forklaring på ate
er fornuftig slik det er.
- Eksempler på gode navn for s:
- 2 poeng: helhetsvurdring (bruk av faguttrykk, forståelse av sammenhenger etc).
Den nederste funksjonen (f)
- 2 poeng: forklaring/forståelse («finner elementet som forekommer flest ganger i
a
»). - 2 poeng: godt funksjonsnavn og parameternavn
- Eksempler på gode funksjonsnavn:
most_common_element
,find_most_common
,get_most_common_item
,most_common
og lignende. - Eksempler på gode navn for a:
collection
,elements
,items
,chars
,lst
,sequence
,data
,container
,objects
,entries
,values
,characters
,letters
,iterable
, etc. Aksepterer også en forklaring på ata
er fornuftig slik det er.
- Eksempler på gode funksjonsnavn:
- 2 poeng: lokale variabelnavn
- Eksempler på gode navn for x:
most_common_so_far
,most_common
,current_most_common
. Aksepterer ogsåbest
,winner
,result
,res
og lignende. - Eksempler på gode navn for y:
current_element
,element
,e
,candidate
,current
,item
,entry
,value
,obj
,letter
,e
etc.
- Eksempler på gode navn for x:
- 2 poeng: helhetsvurdering (bruk av faguttrykk, forståelse av sammenhenger etc).
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 = [3, 3, 3]
b = [a, 3, 3]
- 1 poeng hvis
a
er korrekt (a == [3, 3, 3]
). - 1 poeng hvis
b
er korrekt (b == [[3, 3, 3], 3, 3]
). - 1 poeng hvis
a
er likb[0]
(a == b[0]
). - 2 poeng hvis
a
ogb[0]
viser til samme objekt (a is b[0]
).
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 == [3, 3, 3],
b == [[3, 3, 3], 3, 3],
a == b[0],
(a is b[0]) * 2,
])
# PS: i kontekst av matematisk aritmetikk regnes True som 1 og False som 0.
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], [1, 2, 3]]
b = [[1, 2, 3]] * 2
temp1 = [1, 2, 3]
temp2 = [1, 2, 3]
temp3 = [1, 2, 3]
a = [temp1, temp2]
b = [temp3, temp3]
a = [[1, 2, 3], [1, 2, 3]]
b = [[1, 2, 3]]
b.append(b[0])
a = []
a.append([1, 2, 3])
a.append([1, 2, 3])
b = []
b.append([1, 2, 3])
b.append(b[0])
- 1 poeng hvis
a
er korrekt (a == [[1, 2, 3], [1, 2, 3]]
). - 1 poeng hvis
b
er korrekt (b == [[1, 2, 3], [1, 2, 3]]
). - 1 poeng hvis
b[0]
ogb[1]
viser til samme objekt (b[0] is b[1]
). - 1 poeng hvis
a[0]
oga[1]
er like, men viser til ulike objekter (a[0] == a[1] and a[0] is not a[1]
). - 1 poeng hvis ingen av elementene i
b
viser til et element ia
(b[0] is not a[0] and b[0] is not a[1] and b[1] is not a[0] and b[1] is not a[1]
), selv om alle elementene i a og b er like.
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 == [[1, 2, 3], [1, 2, 3]],
b == [[1, 2, 3], [1, 2, 3]],
b[0] is b[1],
(a[0] == a[1]) and (a[0] is not a[1]),
(
((b[0] is not a[0]) and (b[0] == a[0]))
and ((b[0] is not a[1]) and (b[0] == a[1]))
and ((b[1] is not a[0]) and (b[1] == a[0]))
and ((b[1] is not a[1]) and (b[1] == a[1]))
)
])
# PS: i kontekst av matematisk aritmetikk regnes True som 1 og False som 0.
Anta at du har en aktivitetslogg for et hotell. Aktivitetsloggen er en semikolon-separert csv-fil der hver rad representerer én hendelse. Kolonnene i csv-filen er
- tidspunkt (formattert som yyyy-MM-dd HH:mm)
- hendelsestype (enten «innsjekk» eller «utsjekk»)
- romnummer
- navn på gjesten
Eksempel på de første par linjene i filen:
tidspunkt;hendelsestype;romnummer;navn på gjest
2024-01-01 16:34;innsjekk;201;Donald
2024-01-01 17:15;innsjekk;302;Dolly
2024-01-02 09:14;utsjekk;201;Donald
2024-01-02 10:45;innsjekk;201;Magica fra Tryll
2024-01-02 10:59;utsjekk;302;Dolly
Anta at hendelsene allerede ligger i sortert rekkefølge basert på tidspunkt, og at ingen var sjekket inn på hotellet på tidspunktet loggen begynner. Du kan også anta at loggfilen er konsistent (utsjekk har alltid samme navn på personen som forrige gang det var innsjekk på samme romnummer; og det er alltid annenhver innsjekk og utsjekk om vi kun ser på ett rom av gangen).
Det er tre deloppgaver:
-
(5 poeng) Skriv en funksjon load_logfile(path) hvor
path
er en parameter for filnavnet til loggfilen. Funksjonen skal returnere innholdet i filen på et format egnet for videre prosessering i Python. Du kan velge selv om du vil returnere en 2D-liste eller en liste av oppslagsverk. -
(10 poeng) Skriv en funksjon current_guest_count(table) hvor
table
er en liste på det formatet du valgte i forrige deloppgave. Funksjonen skal returnere antallet gjester som er sjekket inn på hotellet når loggfilen tar slutt.
Eksempel: dersom filen kun bestod av linjene i eksempelet over, skal funksjonen returnere
1
: fordi det er én gjest (Magica fra Tryll) som er sjekket inn når filen tar slutt.
- (10 poeng) Skriv en funksjon get_guests(table, time) hvor
table
er en liste på det formatet du valgte i første deloppgave, ogtime
er en streng som representerer et tidspunkt på samme format som brukes i loggfilen. Funksjonen skal returnere en liste over hvilke gjester som er sjekket inn og hvilke rom de befinner seg på. Mer konkret: funksjonen skal returnere en liste av tupler, der hver tuple består av (navn, romnummer) for en person som er sjekket inn på det gitte tidspunktet.
Eksempel: dersom filen kun bestod av linjene i eksempelet over, og det angitte tidspunktet er
'2024-01-02 10:50'
skal funksjonen returnere listen[('Magica fra Tryll', '201'), ('Dolly', '302')]
.
import csv
from pathlib import Path
# Svaret på denne deloppgaven er basert på kursnotater om
# standardbiblioteket, avsnitt om CSV/DictReader
def load_file(path):
with Path(path).open('rt', encoding='utf-8', newline='') as f:
reader = csv.DictReader(f, delimiter=';')
return list(reader)
def current_guest_count(table):
count = 0
for event in table:
if event['hendelsestype'] == 'innsjekk':
count += 1
elif event['hendelsestype'] == 'utsjekk':
count -= 1
return count
def get_guests(table, time):
currently_checked_in = {}
for event in table:
if event['tidspunkt'] > time:
break
elif event['hendelsestype'] == 'innsjekk':
currently_checked_in[event['romnummer']] = event['navn på gjest']
elif event['hendelsestype'] == 'utsjekk':
currently_checked_in[event['romnummer']] = None
result = []
for room in currently_checked_in:
if currently_checked_in[room] is not None:
result.append((currently_checked_in[room], room))
return result
def load_file(path):
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
lines = content.splitlines()
table = []
for line in lines:
line = line.strip()
row = line.split(';')
table.append(row)
return table
def current_guest_count(table):
count = 0
for event in table:
if event[1] == 'innsjekk':
count += 1
elif event[1] == 'utsjekk':
count -= 1
return count
def get_guests(table, time):
currently_checked_in = {}
for event in table[1:]:
if event[0] > time:
break
elif event[1] == 'innsjekk':
currently_checked_in[event[2]] = event[3]
elif event[1] == 'utsjekk':
currently_checked_in[event[2]] = None
result = []
for room in currently_checked_in:
if currently_checked_in[room] is not None:
result.append((currently_checked_in[room], room))
return result
PS: denne løsningen scorer veldig dårlig på lesbarhet og kodestil, og ville blitt trukket alle poengene under punktet ‘helhetsvurdering’ om den ikke var utstyrt med ekstra forklaringer.
def load_file(path):
with open(path, 'r', encoding='utf-8') as f:
return [r.strip().split(';') for r in f][1:]
def current_guest_count(table):
return sum(2*('i' in e[1])-1 for e in table)
def get_guests(table, time):
d = {}
for e in [e for e in table if e[0] <= time]:
d[e[2]] = e[3] if 'i' in e[1] else None
return [(n, r) for r, n in d.items() if n is not None]
Merk at sammenligningen av tidspunkt er mulig å gjøre som en streng-sammenligning uten å gå via datetime. Dette er mulig siden tidspunktene er formattert med mest signifikante enheter først, og med fast lengde på alle deler av tidspunktet.
load_file (5 poeng)
I deloppgaven om load_file finnes svaret mer eller mindre direkte i kursnotatene som var vedlagt eksamen. Koden må tilpasses til riktig seperator (semikolon) og variabel for filnavn.
- 1 poeng: åpner filen gitt av path og leser innholdet
- 1 poeng: deler opp linjene/radene på en eller annen måte (f. eks.
.splitlines()
,.split('\n')
eller konvertering til liste hvis bruk avcsv
-modulen for å lese filen,list(reader_object)
) - 1 poeng: tar hensyn til at semikolon skiller kolonner (f. eks.
.split(';')
på hver linje, eller bruk av navngitte parametre hvis bruk av csv-modulencsv.reader(f, delimiter=';')
/csv.DictReader(f, delimiter=';')
) - 1 poeng: returnerer en fornuftig sammensatt datastruktur
- 1 poeng: helhetsvurdering
Det trekkes ikke poeng for
- å ikke angi
encoding='utf-8'
ved åpning av filen (tekstformat er ikke oppgitt).- å ikke angi
newline=''
ved åpning av filen ved bruk avcsv
-modulen. Selv om dette egentlig alltid skal gjøres når man benytter csv-modulen, vil det fungere fint uten her.- å glemme å importere
csv
-modulen ellerPath
-klassen frapathlib
-modulen selv om disse brukes.
current_guest_count (10 poeng)
- 2 poeng: løkke over radene i tabellen
- 2 poeng: sjekker om hendelsestype er innsjekk eller utsjekk
- 2 poeng: book-keeping: en eller annen form for telling av antall gjester
- 4 poeng: helhetsvurdering
En mulig løsning vil være denne funksjonen:
def current_guest_count(table): return len(get_guests(table, '9999-12-31 23:59'))
En slik løsning kan gi full pott, men man må da vurdere get_guests-funksjonen i henhold til sensurkriteriene for denne deloppgaven.
get_guests (10 poeng)
- 2 poeng: fornuftig betingelse benyttet for å ignorere hendelser som skjer etter det gitte tidspunktet
- 2 poeng: sammenligner tidspunktet i raden med tidspunktet gitt som parameter på en fornuftig måte
- 3 poeng: book-keeping: en eller annen form for lagring av gjester som er sjekket inn på det gitte tidspunktet, typisk et oppslagsverk med romnummer som nøkkel og navn på gjest som verdi, samt korrekt oppdatering av dette i løkken.
- 1 poeng: koden håndterer at samme gjest sjekker inn og ut flere ganger
- 2 poeng: helhetsvurdering
Helhetsvurdering
Helhetsvurdering er basert på helhetlig korrekthet og forståelse av oppgaven.
- For besvarelser som i grove trekk er korrekte og får god uttelling på de øvrige kulepunktene, kan man gi litt trekk her for logiske feil (hvis noen) og dersom koden har dårlig struktur og/eller er spesielt vaskelig å lese.
- For besvarelser som får dårlig uttelling på de øvrige kulepunkt i forhold til hva besvarelsen demonstrerer av forståelse, kan sensor her gi en skjønnsmessig oppjustering. Dette gjelder for eksempel i tilfeller der det er skrevet kommentarer som viser intensjonen med en kodesnutt selv om gjennomføringen ikke var vellykket.
Om bruk av datetime-modulen
- Det er ikke nødvendig å benytte datetime eller andre kompliserte (hjelpe-)metoder for å sammenligne tidspunktene; det er fullt mulig å sammenligne tidspunktene direkte som strenger, siden de er formattert med mest signifikante enheter først og med fast lengde på alle deler av tidspunktet.
- Det vil ikke gi noe poengtrekk å bruke datetime-modulen for å sammenligne tidspunktene, selv om det er gjort små feil i konvertering fra streng til datetime.
- Det vil ikke gi poengtrekk å benytte en egen hjelpefunksjon for å sammenligne to tidspunkt. Små detaljfeil i implementasjonen av en slik hjelpefunksjon vil heller ikke gi poengtrekk.
- Å gjøre en innviklet/komplisert sammenligning av tidspunkter eller konvertering til datetime direkte i løkken uten å benytte en hjelpefunksjon vil gi trekk i helhetsvurderingen for dårlig struktur/lesbarhet.