6.3 Algoritme-metoder i klasser
De én-to «smarte» metodene på toppen av modellen: finn den beste/travleste, tell, unngå duplikater, beregn match — de samme løkkemønstrene fra Del 3, nå inne i klassemetoder.
det er ofte den høyest vektede deloppgaven i hele Oppgave 4.
Formuleringene går igjen: «finn den travleste dagen», «hvilken destinasjon er mest populær», «sett inn i første ledige plass i prioritert rekkefølge». Alle er den samme øvelsen: én løkke over samlingen, med et lite regnskap ved siden av.
Sjangeren er F — stor klassemodellering, altså å bygge samarbeidende klasser fra en beskrevet oppgave. Dette må sitte — bokas høyeste prioritetsnivå.
Den gode nyheten er at du kan mønstrene fra før. Teller, akkumulator, flagg og finn-beste kommer alle fra del 3. Det eneste nye er at løkka nå går over self._elementer, og at verdiene hentes med et metodekall.
Kapitlet er langt (~55 min) og er delt i fire løkker med tidsanslag.
Forkunnskaper — sist du var her
Kapitlet bygger på kap. 6.2 — Samarbeidende klasser, og gjenbruker mønstrene fra kap. 3.1 — Lister og
kap. 3.2 — Ordbøker.
De tre mønstrene som kommer igjen, friskes opp med kjørt kode.
1. Teller og akkumulator:
tall = [4, 9, 2, 7]
antall = 0 total = 0 for x in tall:
if x > 3:
antall = antall + 1 total = total + x print(antall, total)Utskrift:
3 222. Finn beste — to variabler som oppdateres sammen:
tall = [4, 9, 2, 7]
beste = None beste_verdi = 0 for x in tall:
if x > beste_verdi:
beste = x beste_verdi = x print(beste, beste_verdi)Utskrift:
9 93. Teller-ordbok med if k not in d::
d = {}
for navn in ["ada", "nora", "ada"]:
if navn not in d:
d[navn] = 0 d[navn] = d[navn] + 1 print(d)Utskrift:
{'ada': 2, 'nora': 1}Nå flytter vi alle tre inn i en klassemetode.
Løkke 1 — Finn beste, inne i en klasse (~15 min)
«Hvilken sal er travlest?» «Hvilken forfatter har flest utlån?» «Hvilken rett har solgt mest?»
Alle tre er det samme spørsmålet, og svaret er det samme mønsteret: løp gjennom samlingen, hold styr på den beste så langt, og oppdater to variabler når du finner en bedre.
Bokas attende kodemønster, og det som oftest bærer den tyngst vektede deloppgaven i Oppgave 4:
def beste_element(self):
beste = None beste_verdi = 0 for e in self._elementer:
if e.hent_verdi() > beste_verdi:
beste = e beste_verdi = e.hent_verdi()
return besteBlokken er en mal med plassholdernavn og er merket som ikke-kjørbar.
Fire regler, og hver av dem er verdt poeng:
1. To variabler, satt FØR løkka. beste = None og beste_verdi = 0.
2. Begge oppdateres SAMMEN, inne i samme if. Oppdaterer du bare den ene,
finner løkka feil element.
3. return ETTER løkka, aldri inne i den — det ville gitt det første
elementet over grensen, ikke det største.
4. beste = None er det ærlige svaret på en tom samling. Den som kaller,
tester med if svar != None:.
Skal du finne det minste, snus sammenligningen til <, og startverdien må settes høyt nok — eller til None, med en egen sjekk i første runde.
beste holder elementet, beste_verdi holder verdien du sammenligner med.Du kunne klart deg med én, ved å skrive if e.hent_verdi() > beste.hent_verdi():
— men da må beste allerede peke på noe i første runde, og på en tom samling stopper programmet.
To variabler er dessuten raskere å lese, og de gjør fasiten lettere å rette. Det er formen fagets egne løsningsforslag bruker.
Den vanligste feilen er å oppdatere bare den ene. Glemmer dubeste_verdi = e.hent_verdi(), blir grensen stående på 0, og løkka ender med det
siste elementet over 0 i stedet for det største.
beste_verdi = 0 virker når alle verdiene er positive — antall, poeng, besøk.Kan verdiene være negative, er 0 for høyt: et element med verdien -3 ville aldri blitt valgt, og metoden ville returnert None på en samling som ikke er tom.
Da er svaret å la den første runden sette startverdien:
beste = None for e in self._elementer:
if beste == None or e.hent_verdi() > beste.hent_verdi():
beste = eBlokken er en mal og er merket som ikke-kjørbar.
I denne boka er verdiene nesten alltid tellinger, så 0 holder — men det er verdt å nevne valget i en kommentar hvis oppgaven åpner for negative tall.
Et treningssenter holder salene i en ordbok. Skriv en metode som returnerer salen med flest påmeldte, og en som returnerer navnet på den.
Finn-beste-malen satt rett inn i en klassemetode:
class Sal:
def __init__(self, navn, kapasitet):
self._navn = navn self._kapasitet = kapasitet self._paameldte = 0
def hent_navn(self):
return self._navn
def hent_paameldte(self):
return self._paameldte
def meld_paa(self, antall):
self._paameldte = self._paameldte + antall
def __str__(self):
return self._navn + " (" + str(self._paameldte) + ")"
class Senter:
def __init__(self):
self._saler = {}
def legg_til(self, sal):
self._saler[sal.hent_navn()] = sal
def travleste_sal(self):
beste = None beste_antall = 0 for navn in self._saler:
sal = self._saler[navn]
if sal.hent_paameldte() > beste_antall:
beste = sal beste_antall = sal.hent_paameldte()
return beste
s = Senter()
s.legg_til(Sal("Speilsal", 20))
s.legg_til(Sal("Spinning", 14))
s.legg_til(Sal("Yoga", 30))
s._saler["Speilsal"].meld_paa(12)
s._saler["Spinning"].meld_paa(14)
s._saler["Yoga"].meld_paa(9)
print(s.travleste_sal())
print(s.travleste_sal().hent_navn())Utskrift:
Spinning (14)
SpinningHjelpevariabelen sal = self._saler[navn] er verdt å legge merke til. Uten
den måtte du skrevet self._saler[navn].hent_paameldte() tre ganger i samme metode. Én linje ekstra gjør resten mye lettere å lese — og å rette.
At metoden returnerer selve salen og ikke navnet, er et bevisst valg. Den som
kaller, kan da spørre videre: travleste_sal().hent_navn(), ellertravleste_sal().hent_paameldte(). Returnerte metoden bare navnet, ville det vært et blindspor.
Slik ville sensor sett på det: fire ting gir uttelling hver for seg — de to
startverdiene før løkka, løkka over samlingen, at begge variablene oppdateres sammen, og return etter løkka. Hadde du glemtbeste_antall = sal.hent_paameldte(), ville svaret her blitt Yoga — det siste over null.
Merk linjene med s._saler[...]. De rører objektets innside utenfra, som boka
ellers aldri gjør — feilkode #11. De står her bare fordi klassen i dette eksempelet ikke har en finn-metode; i en besvarelse ville du skrevet den først.
(Innstegsoppgave — gjengivelse.) Se på metoden. Blokken er et utdrag av en klasse og er derfor merket som ikke-kjørbar.
def beste(self):
beste = None beste_verdi = 0 for e in self._elementer:
if e.hent_verdi() > beste_verdi:
beste = e return bestea) Hvilken linje mangler, og hvor skal den stå?
b) Hva blir resultatet uten den, på verdiene 4, 9 og 7?
c) Hvorfor står return etter løkka og ikke inne i den?
Løkke 2 — Telle og gruppere gjennom klassen (~14 min)
Den nest vanligste algoritme-metoden er en teller-ordbok: «hvor mange bøker har hver forfatter?», «hvor mange deltakere er det per dag?».
Mønsteret er nøyaktig det fra kap. 3.2. Det eneste nye er at nøkkelen og verdien hentes med metodekall på underobjektene.
Idiomet, nå med objekter i stedet for rådata:
class Bok:
def __init__(self, tittel, forfatter, utlaan):
self._tittel = tittel self._forfatter = forfatter self._utlaan = utlaan
def hent_forfatter(self):
return self._forfatter
def hent_utlaan(self):
return self._utlaan
class Bibliotek:
def __init__(self):
self._boker = []
def legg_til(self, b):
self._boker.append(b)
def utlaan_per_forfatter(self):
d = {}
for b in self._boker:
f = b.hent_forfatter()
if f not in d:
d[f] = 0 d[f] = d[f] + b.hent_utlaan()
return d
bib = Bibliotek()
bib.legg_til(Bok("Sult", "Hamsun", 3))
bib.legg_til(Bok("Vaar", "Undset", 7))
bib.legg_til(Bok("Markens grode", "Hamsun", 5))
print(bib.utlaan_per_forfatter())Utskrift:
{'Hamsun': 8, 'Undset': 7}Tre faste deler: ordboka opprettes før løkka, if f not in d: ogd[f] = 0 før du legger til, og return etter løkka.
Uten if-sjekken stopper programmet med KeyError på den første boka — feilkode
#14.
Varianten der verdien er en liste i stedet for et tall:
class Bok:
def __init__(self, tittel, forfatter):
self._tittel = tittel self._forfatter = forfatter
def hent_tittel(self):
return self._tittel
def hent_forfatter(self):
return self._forfatter
boker = [Bok("Sult", "Hamsun"), Bok("Vaar", "Undset"), Bok("Pan", "Hamsun")]
d = {}
for b in boker:
f = b.hent_forfatter()
if f not in d:
d[f] = []
d[f].append(b.hent_tittel())
print(d)Utskrift:
{'Hamsun': ['Sult', 'Pan'], 'Undset': ['Vaar']}Forskjellen fra telleren er to tegn: [] i stedet for 0, og .append(...) i stedet for +.
Kjenn det igjen i oppgaveteksten på «lag en ordbok fra … til en liste over …»
eller «grupper etter …».
Skriv en metode som returnerer forfatteren med flest utlån til sammen. Bygg først en teller-ordbok, og finn så den største med en eksplisitt løkke.
To mønstre etter hverandre i samme metode — det er den vanligste formen den tyngste deloppgaven i Oppgave 4 har:
class Bok:
def __init__(self, tittel, forfatter, utlaan):
self._tittel = tittel self._forfatter = forfatter self._utlaan = utlaan
def hent_forfatter(self):
return self._forfatter
def hent_utlaan(self):
return self._utlaan
class Bibliotek:
def __init__(self):
self._boker = []
def legg_til(self, b):
self._boker.append(b)
def mest_populaere_forfatter(self):
teller = {}
for b in self._boker:
f = b.hent_forfatter()
if f not in teller:
teller[f] = 0 teller[f] = teller[f] + b.hent_utlaan()
beste = None beste_antall = 0 for f in teller:
if teller[f] > beste_antall:
beste = f beste_antall = teller[f]
return beste
bib = Bibliotek()
bib.legg_til(Bok("Sult", "Hamsun", 3))
bib.legg_til(Bok("Vaar", "Undset", 7))
bib.legg_til(Bok("Pan", "Hamsun", 5))
bib.legg_til(Bok("Naboer", "Bjornson", 2))
print(bib.mest_populaere_forfatter())Utskrift:
HamsunMetoden er to løkker etter hverandre, ikke én nøstet. Første løkke bygger
regnskapet, andre løkke leser det. Å prøve å gjøre begge deler i én løkke er både vanskeligere og lettere å bomme på.
Legg merke til at den andre løkka går over teller, ikke over self._boker.
Det er nettopp derfor telleren måtte bygges først: Hamsun står på to bøker, og summen 3 pluss 5 finnes ikke i noen enkelt bok.
Slik ville sensor sett på det: dette er ofte den høyest vektede deloppgaven,
typisk 8–12 poeng, og de deles i to. Teller-ordboka med if f not in teller: er én halvdel; finn-beste-løkka med to variabler er den andre. Får du bare den første til, har du halve poengsummen.
Snarveien finnes, men den belønnes ikke. Python har ferdige funksjoner sommax(..., key=...) og sorted(...), og en Counter som teller for deg. Denne boka bruker dem ikke, fordi kurset og fasiten belønner grunnformen — og fordi den eksplisitte løkka er det du trenger når oppgaven ber om noe som ikke passer inn i en ferdig funksjon.
— naturlig pausepunkt —
En kantine har retter med navn, pris og antall solgte.
a) Skriv Rett og Kantine med det som trengs, og en metodebestselger(self) som returnerer retten med flest solgte porsjoner.
b) Legg til samlet_omsetning(self).
c) Test med tre retter, og skriv ut både bestselgeren og omsetningen.
Hva skriver programmet ut?
class Okt:
def __init__(self, dag, antall):
self._dag = dag self._antall = antall
def hent_dag(self):
return self._dag
def hent_antall(self):
return self._antall
class Senter:
def __init__(self):
self._okter = []
def legg_til(self, o):
self._okter.append(o)
def per_dag(self):
d = {}
for o in self._okter:
dag = o.hent_dag()
if dag not in d:
d[dag] = 0 d[dag] = d[dag] + o.hent_antall()
return d
def travleste_dag(self):
d = self.per_dag()
beste = None beste_antall = 0 for dag in d:
if d[dag] > beste_antall:
beste = dag beste_antall = d[dag]
return beste
s = Senter()
s.legg_til(Okt("mandag", 14))
s.legg_til(Okt("tirsdag", 20))
s.legg_til(Okt("mandag", 9))
s.legg_til(Okt("onsdag", 6))
print(s.per_dag())
print(s.travleste_dag())Løkke 3 — Flagg, duplikater og match (~13 min)
Tre mindre mønstre som dukker opp som deloppgaver: å svare ja eller nei på noe om hele samlingen, å unngå at det samme legges inn to ganger, og å telle treff mot en annen samling.
Mønsteret for «finnes det …?» og «er alle …?»:
class Sal:
def __init__(self, navn, ledige):
self._navn = navn self._ledige = ledige
def hent_ledige(self):
return self._ledige
class Senter:
def __init__(self):
self._saler = []
def legg_til(self, s):
self._saler.append(s)
def finnes_ledig(self):
for s in self._saler:
if s.hent_ledige() > 0:
return True return False
def alle_fulle(self):
for s in self._saler:
if s.hent_ledige() > 0:
return False return True
sen = Senter()
sen.legg_til(Sal("A", 0))
sen.legg_til(Sal("B", 3))
print(sen.finnes_ledig(), sen.alle_fulle())Utskrift:
True False«Finnes det …?» returnerer True inne i løkka og False etter. «Er alle
…?» returnerer False inne i løkka og True etter.
Retur-etter-løkka-linjen er obligatorisk. Glemmer du den, gir metoden None
når løkka går helt gjennom — og None oppfører seg som usant i en if, så feilen er stum.
Å snu de to — returnere True inne i en «er alle»-sjekk — er feilkode #7, for tidlig retur.
Mønsteret for «legg til bare hvis den ikke allerede er der»:
class Klubb:
def __init__(self):
self._navn = []
def meld_inn(self, n):
if n in self._navn:
print(n, "er allerede medlem")
else:
self._navn.append(n)
def antall(self):
return len(self._navn)
k = Klubb()
k.meld_inn("Ada")
k.meld_inn("Nora")
k.meld_inn("Ada")
print(k.antall())Utskrift:
Ada er allerede medlem
2For en liste brukes if n in self._navn:. For en ordbok er problemet borte av seg selv: en nøkkel kan bare finnes én gang — men da overskrives den gamle verdien uten et pip, så sjekken kan likevel være nødvendig.
Skal du sjekke duplikater blant objekter, sammenlign en verdi de har, ikke objektene selv: if navn in self._navn: der self._navn er en liste av navn, eller en løkke som spør hvert objekt.
Mønsteret for «hvor godt passer disse to sammen?»: tell positive treff, trekk fra negative.
mine = ["sjakk", "kolonier", "ordjakt"]
dine = ["kolonier", "yatzy", "sjakk"]
poeng = 0 for spill in mine:
if spill in dine:
poeng = poeng + 1 else:
poeng = poeng - 1 print(poeng)Utskrift:
1To treff og ett bom gir 1. Formen er en akkumulator med to grener, og den er lett å utvide: ulik vekt på treff og bom, eller bare positive poeng.
Kjenn den igjen i oppgaveteksten på «regn ut hvor godt to … passer sammen» eller
«gi ett poeng for hver felles …».
Et bibliotek skal sette en innlevert bok i første ledige hylle, i en gitt prioritert rekkefølge. Skriv metoden.
Mønsteret er en løkke med et flagg som sier om jobben er gjort:
class Hylle:
def __init__(self, navn, plasser):
self._navn = navn self._plasser = plasser self._boker = []
def hent_navn(self):
return self._navn
def er_ledig(self):
return len(self._boker) < self._plasser
def sett_inn(self, tittel):
self._boker.append(tittel)
def __str__(self):
return self._navn + ": " + str(len(self._boker)) + "/" + str(self._plasser)
class Bibliotek:
def __init__(self):
self._hyller = []
def legg_til_hylle(self, h):
self._hyller.append(h)
def sett_inn_ledig(self, tittel):
ferdig = False for h in self._hyller:
if h.er_ledig() and ferdig == False:
h.sett_inn(tittel)
ferdig = True if ferdig == False:
print("Ingen ledig hylle for", tittel)
return ferdig
def skriv_alle(self):
for h in self._hyller:
print(h)
bib = Bibliotek()
bib.legg_til_hylle(Hylle("H1", 1))
bib.legg_til_hylle(Hylle("H2", 2))
bib.sett_inn_ledig("Sult")
bib.sett_inn_ledig("Vaar")
bib.sett_inn_ledig("Pan")
bib.sett_inn_ledig("Naboer")
bib.skriv_alle()Utskrift:
Ingen ledig hylle for Naboer
H1: 1/1
H2: 2/2Rekkefølgen i lista er prioriteringen. H1 fylles først, så H2. Skal
prioriteringen være en annen, legger du hyllene inn i en annen rekkefølge — eller oppgaven oppgir en egen prioritetsliste.
ferdig-flagget er det som gjør at boka settes inn bare én gang. Uten det
ville løkka satt den inn i hver ledige hylle den kom til. Betingelsenh.er_ledig() and ferdig == False sjekker begge deler.
Alternativet er return inne i løkka, som ville vært kortere. Boka bruker
flagget her fordi metoden skal gjøre noe etter løkka — skrive en beskjed hvis ingen hylle var ledig — og fordi flagget er formen fagets egne løsningsforslag bruker for dette mønsteret.
Slik ville sensor sett på det: fem ting gir uttelling — flagget satt tilFalse før løkka, løkka i prioritert rekkefølge, den todelte betingelsen,ferdig = True etter innsettingen, og håndteringen av «ingen ledig» etter løkka.
Glemmer du flagget, er strukturen riktig og trekket lite — men boka havner fire steder.
En sykkelutleie har stativer i en liste.
a) Skriv Stativ med navn, antall plasser og en liste av sykler, meder_ledig() og sett_inn(self, kode).
b) Skriv Utleie med en metode finnes_ledig(self) som svarer True ellerFalse, og en parker(self, kode) som setter sykkelen i første ledige stativ og skriver en beskjed hvis ingen er ledige.
c) Test med to stativer med til sammen tre plasser, og fire sykler.
Et bibliotek har bøker med tittel og forfatter.
a) Skriv en metode boker_per_forfatter(self) som returnerer en ordbok fra
forfatter til en liste over titlene.
b) Skriv forfatter_med_flest(self) som returnerer navnet på den forfatteren
som har flest bøker.
c) Test med fem bøker fordelt på tre forfattere.
Løkke 4 — Kant-tilfellene (~12 min)
Algoritme-metoder feller folk på kantene: den tomme samlingen, den ene enkeltverdien, og de to som er like store.
Det er ikke vanskelig å håndtere dem — men det må gjøres bevisst, og det gir poeng.
Feilkode #6 er at metoden ikke tåler en tom eller minimal samling.
Den tomme samlingen. Finn-beste-malen svarer None, og det er riktig — men
den som kaller, må sjekke. senter.travleste_sal().hent_navn() på et tomt senter stopper programmet medAttributeError: 'NoneType' object has no attribute 'hent_navn'.
Ett element. Løkka går én runde, og svaret er det ene elementet. Det går bra
med malen — men bare fordi startverdien er 0 og verdien er positiv.
To like store. Malen bruker > og beholder derfor det første. Med >=
ville den beholdt det siste. Begge er forsvarlige, men oppgaven kan si hvilken som gjelder, og da må du lese sakte.
Alle verdier er null. Med beste_verdi = 0 og > blir svaret None, selv om
samlingen ikke er tom. Er det et problem, dokumentér valget i en kommentar — eller la første runde sette startverdien.
Motgiften er en huskeliste: hva svarer metoden på tom samling, på ett element,
og på to like? Tre spørsmål, ti sekunder, og de fanger de fleste trekkene.
> Et treningssenter har medlemmer og økter. Hvert medlem har et navn og et antall
> besøk. Hver økt har en dag, et salnavn og et antall deltakere. Senteret skal
> kunne si hvilken dag som er travlest, hvilket medlem som har flest besøk, om det
> finnes en økt med færre enn fem deltakere, og hvor mange ulike saler som er i
> bruk.
Skriv metodene.
Fire metoder, fire mønstre. Klassene først:
class Medlem:
def __init__(self, navn, besok):
self._navn = navn self._besok = besok
def hent_navn(self):
return self._navn
def hent_besok(self):
return self._besok
class Okt:
def __init__(self, dag, sal, deltakere):
self._dag = dag self._sal = sal self._deltakere = deltakere
def hent_dag(self):
return self._dag
def hent_sal(self):
return self._sal
def hent_deltakere(self):
return self._deltakereSå senteret, med de fire metodene:
class Medlem:
def __init__(self, navn, besok):
self._navn = navn self._besok = besok
def hent_navn(self):
return self._navn
def hent_besok(self):
return self._besok
class Okt:
def __init__(self, dag, sal, deltakere):
self._dag = dag self._sal = sal self._deltakere = deltakere
def hent_dag(self):
return self._dag
def hent_sal(self):
return self._sal
def hent_deltakere(self):
return self._deltakere
class Senter:
def __init__(self):
self._medlemmer = []
self._okter = []
def legg_til_medlem(self, m):
self._medlemmer.append(m)
def legg_til_okt(self, o):
self._okter.append(o)
def travleste_dag(self):
d = {}
for o in self._okter:
dag = o.hent_dag()
if dag not in d:
d[dag] = 0 d[dag] = d[dag] + o.hent_deltakere()
beste = None beste_antall = 0 for dag in d:
if d[dag] > beste_antall:
beste = dag beste_antall = d[dag]
return beste
def flittigste_medlem(self):
beste = None beste_besok = 0 for m in self._medlemmer:
if m.hent_besok() > beste_besok:
beste = m beste_besok = m.hent_besok()
return beste
def finnes_liten_okt(self):
for o in self._okter:
if o.hent_deltakere() < 5:
return True return False
def antall_ulike_saler(self):
saler = []
for o in self._okter:
if o.hent_sal() not in saler:
saler.append(o.hent_sal())
return len(saler)
s = Senter()
s.legg_til_medlem(Medlem("Ada", 18))
s.legg_til_medlem(Medlem("Nora", 25))
s.legg_til_medlem(Medlem("Per", 7))
s.legg_til_okt(Okt("mandag", "Speilsal", 14))
s.legg_til_okt(Okt("mandag", "Spinning", 9))
s.legg_til_okt(Okt("tirsdag", "Speilsal", 20))
s.legg_til_okt(Okt("onsdag", "Yoga", 3))
print("Travleste dag:", s.travleste_dag())
print("Flittigste:", s.flittigste_medlem().hent_navn())
print("Liten okt finnes:", s.finnes_liten_okt())
print("Ulike saler:", s.antall_ulike_saler())Utskrift:
Travleste dag: mandag
Flittigste: Nora
Liten okt finnes: True
Ulike saler: 3Fire mønstre, fire deloppgaver, og de rettes hver for seg.
travleste_dag er teller-ordbok pluss finn-beste — to løkker etter hverandre.
Mandag har 14 pluss 9, altså 23, mot tirsdags 20.
flittigste_medlem er ren finn-beste, og den returnerer medlemmet, slik at den som kaller, kan spørre videre.
finnes_liten_okt er flagg med tidlig retur: True inne i løkka, False etter.
antall_ulike_saler er duplikat-mønsteret: bygg en liste over dem du har sett, og tell lengden. Speilsal telles bare én gang, selv om den brukes to dager.
Slik ville sensor sett på det: dette er en 15–20-poengs deloppgave i den store
oppgaven, og poengene ligger jevnt fordelt. Hver metode er selvstendig, så du kan hoppe over en du står fast på og fortsatt få de tre andre.
Kant-tilfellene: på et tomt senter gir de to første None, den tredje False
og den fjerde 0. Alle fire er fornuftige svar — men den som kallerflittigste_medlem().hent_navn(), må sjekke først.
Hva skriver programmet ut?
class Vare:
def __init__(self, navn, antall):
self._navn = navn self._antall = antall
def hent_navn(self):
return self._navn
def hent_antall(self):
return self._antall
class Lager:
def __init__(self):
self._varer = []
def legg_til(self, v):
self._varer.append(v)
def storste(self):
beste = None beste_antall = 0 for v in self._varer:
if v.hent_antall() > beste_antall:
beste = v return beste
lager = Lager()
lager.legg_til(Vare("skrue", 120))
lager.legg_til(Vare("bolt", 45))
lager.legg_til(Vare("skive", 200))
lager.legg_til(Vare("mutter", 80))
print(lager.storste().hent_navn())En brettspillklubb har medlemmer, og hvert medlem har en liste over spill det liker.
a) Skriv Medlem med navn og en liste over spilltitler, medlegg_til_spill(self, tittel) og liker(self, tittel) som svarer True ellerFalse.
b) Skriv match(self, annet) i Medlem, som gir ett poeng for hvert spill
begge liker og trekker ett for hvert bare den ene liker — regnet fra dette medlemmets liste.
c) Skriv Klubb med en metode beste_match(self, medlem) som finner det
andre medlemmet med høyest match.
d) Test med tre medlemmer.
En medstudent har skrevet:
class Sal:
def __init__(self, navn, paameldte):
self._navn = navn self._paameldte = paameldte
def hent_navn(self):
return self._navn
def hent_paameldte(self):
return self._paameldte
class Senter:
def __init__(self):
self._saler = []
def legg_til(self, s):
self._saler.append(s)
def travleste(self):
for s in self._saler:
beste = None beste_antall = 0 if s.hent_paameldte() > beste_antall:
beste = s beste_antall = s.hent_paameldte()
return bestea) Metoden har to feil. Hvilke?
b) Gi et konkret moteksempel.
c) Skriv den rettede metoden.
Fem feil koster nesten alle poengene som mistes på algoritme-metoder:
- Bare den ene variabelen oppdatert i finn-beste. Grensen blir stående, og
løkka ender med det siste elementet over 0 i stedet for det største. Stum feil.
- Startverdiene inne i løkka. De nullstilles hver runde, og regnskapet
forsvinner.
- #7 — return inne i løkka. Metoden svarer på det første elementet i stedet
for å se hele samlingen.
- #14 — teller-ordbok uten if k not in d:. Programmet stopper med KeyError
på den første nøkkelen.
- #6 — kant-tilfellene ikke tenkt gjennom. Tom samling gir None, og den som
kaller, må sjekke. To like store: > beholder den første, >= den siste.
Og én som ikke gir feil svar, men som trekker: snarveiene. max(..., key=...),sorted(...) og ferdige tellere finnes, men kurset og fasiten belønner grunnformen — lær den eksplisitte løkka først.
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
En metode på toppen av modellen som regner ut noe av samlingen: finn den travleste, tell de ulike, avgjør om noe finnes.
Den er ofte den høyest vektede deloppgaven i Oppgave 4, og den er der de vanskelige poengene ligger — resten av modellen er mønstre du kan skrive uten å tenke.
Alle formene er de samme løkkemønstrene fra del 3, flyttet inn i en klasse.
To variabler før løkka, samlet oppdatering inne i den, return etter.
beste holder elementet, beste_verdi holder verdien du sammenligner med.
Oppdaterer du bare den ene, ender løkka med det siste elementet over grensen i stedet for det største.
Det er den vanligste stumme feilen i hele Del 6, fordi metoden fortsatt returnerer noe som ser fornuftig ut.
Fordi løkka må se hele samlingen før den kan svare hvem som er best.
Et return inne i løkka gir det første elementet over grensen. Det er feilkode #7, for tidlig retur, og den kommer igjen i hvert eneste flagg-mønster.
Unntaket er «finnes det …?»-mønsteret, der et tidlig return True er nettopp det riktige — men da må return False stå etter løkka.
beste_verdi = 0 virker når verdiene er positive tellinger, som de nesten alltid er i denne boka.Kan verdiene være negative, er 0 for høyt: metoden ville svart None på en samling som ikke er tom. Da lar du første runde sette startverdien, medif beste == None or ….
Er alle verdiene null, gir > og startverdi 0 også None. Dokumentér valget i en kommentar hvis oppgaven åpner for det.
return beste der beste er selve elementet, ikke beste.hent_navn().Da kan den som kaller, spørre videre: travleste_sal().hent_navn() ellertravleste_sal().hent_paameldte(). Returnerer metoden bare navnet, er det et blindspor.
Unntaket er når oppgaveteksten uttrykkelig ber om navnet. Les hva den spør etter.
if f not in d: og d[f] = 0 før d[f] = d[f] + verdi.Nøkkelen og verdien hentes med metodekall på underobjektene: b.hent_forfatter() og b.hent_utlaan().
Uten if-sjekken stopper programmet med KeyError på den første nøkkelen — feilkode #14.
Telleren med to tegn byttet ut: [] i stedet for 0, og .append(...) i stedet for +.
Resultatet er en ordbok fra nøkkel til liste:{'Hamsun': ['Sult', 'Pan']}.
Kjenn det igjen i oppgaveteksten på «lag en ordbok fra … til en liste over …»
eller «grupper etter …».
Formen den tyngste deloppgaven ofte har: bygg et regnskap i én løkke, og les det i en annen.
Å prøve å gjøre begge deler i samme løkke er både vanskeligere og lettere å bomme på — og det gir ingen ekstra poeng.
Legg gjerne den første løkka i sin egen metode, og kall den med self. foran.
Da kan begge delene brukes hver for seg, og deloppgavene rettes uavhengig.
«Finnes det …?»: return True inne i løkka, return False etter.
«Er alle …?»: return False inne, return True etter.
Å bytte om de to er feilkode #7: en «er alle»-sjekk som returnerer True på det første treffet, svarer ja så snart ett element passer.
Linjen etter løkka er obligatorisk. Glemmer du den, gir metoden None, ogNone oppfører seg som usant i en if — så feilen er stum.
Mønsteret for «gjør noe med det første elementet som passer, og si fra hvis ingen gjør det».
ferdig = False før løkka, betingelsen if … and ferdig == False:, ogferdig = True etter handlingen.
Alternativet er return inne i løkka. Flagget brukes når metoden skal gjøre noe
etter løkka — og det er formen fagets egne løsningsforslag bruker for
prioritert innsetting.
Rekkefølgen elementene ligger i, er prioriteringen. Første ledige i lista blir valgt.
Skal prioriteringen være en annen enn innsettingsrekkefølgen, oppgir oppgaven som regel en egen liste å løpe over.
Kombinert med ferdig-flagget er dette «sett inn i første ledige plass»-mønsteret som går igjen i settene.
if x in self._liste: før append, eller if x not in d: for en ordbok.For en liste må du sjekke selv. For en ordbok er problemet borte — men da
overskrives den gamle verdien uten et pip, så sjekken kan likevel trengs.
Skal du telle ulike verdier, bygg en liste over dem du har sett, og returnerlen(...).
En akkumulator med to grener: + 1 for hvert treff, - 1 for hvert bom.
poeng = 0 for x in self._mine:
if annet.liker(x):
poeng = poeng + 1 else:
poeng = poeng - 1 return poengBlokken er en mal og er merket som ikke-kjørbar.
Regnestykket er ikke symmetrisk: det går over dette objektets liste. A mot
B og B mot A kan gi ulike svar. Les oppgaveteksten for å se hvilken vei den ber om.
if m != medlem: når du sammenligner alle elementer mot ett av dem.Uten den blir elementet sammenlignet med seg selv, og det gir alltid full match — så svaret blir alltid «seg selv».
Det er et selvstendig poeng i den typen oppgave, og det er lett å glemme under tidspress.
At metoden ikke tåler en tom eller minimal samling.
Tre spørsmål tar de fleste trekkene: hva svarer metoden på tom samling, på
ett element, og på to like store?
Finn-beste gir None på tom samling — riktig, men den som kaller, må sjekke.> beholder den første av to like, >= den siste.
Det ærlige svaret når en metode ikke finner noe: en tom samling, eller ingen som oppfyller kravet.
Den som kaller, tester med if svar != None:. Gjør du ikke det, stopper programmet medAttributeError: 'NoneType' object has no attribute 'hent_navn'.
Den feilmeldingen er verdt å kjenne igjen: den betyr nesten alltid at et søk gaNone og at sjekken manglet.
sal = self._saler[navn] øverst i løkkekroppen, i stedet for å skriveself._saler[navn] tre ganger.Én linje ekstra gjør resten mye lettere å lese — og å rette. Det gir ingen ekstra poeng i seg selv, men det reduserer sjansen for skrivefeil betraktelig.
Samme teknikk brukes når en lenket kjede blir lang: del den opp med en variabel underveis.
self.per_dag() inne i travleste_dag() — en metode kan kalle en annen på samme objekt.Gevinsten er at regnskapet bygges ett sted. Endrer du hvordan det regnes, treffer endringen begge metodene.
Navnene må være ulike. En metode som kaller en metode med samme navn, ser ut
som om den kaller seg selv — og det er rekursjon, som er utenfor pensum i dette emnet.
Python har max(..., key=...), sorted(...), sum(...) og ferdige tellere som løser flere av mønstrene på én linje.
Denne boka bruker dem ikke, og det er et bevisst valg. Fagets egne
løsningsforslag skriver den eksplisitte løkka, fordi det er løkka som er ferdigheten kurset måler.
Og fordi den eksplisitte formen er den eneste som virker når oppgaven ber om noe en ferdig funksjon ikke dekker — som å telle poeng med to grener, eller å hoppe over elementet selv.
Nøkkelen eller verdien? Objektet eller navnet? Antallet eller elementet?
Feilen er lett å gjøre i en finn-beste-løkke, der du har begge deler liggende:return beste gir elementet, return beste_verdi gir tallet.
Les siste setning i oppgaveteksten en gang til før du skriver return. Det tar fem sekunder og redder et helt poeng.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.