Županske volitve
Testi
Obvezna naloga
Da bi imeli vsi županske kandidatke enake možnosti, so pripravili glasovnice z različnimi vrstnimi redi. Tako lahko nekdo dobi seznam kandidatk
["Anna", "Berta", "Cilka"]
in nekdo drug["Cilka", "Anna", "Berta"]
. Če na prvi glasovnici obkrožimo Cilko, takšno glasovnico opišemo z[False, False, True]
. Na drugi glasovnici pa je nekdo obkrožil Cilka in Berta (zaradi česar je glasovnica sicer neveljavna), kar predstavimo z[True, False, True]
.
- Napiši funkcijo
glasov(obkrozeno)
, ki kot argument dobi seznam, ki pove, kaj je obkroženo in kaj ne, ter vrne število obkroženih kandidatk. Klicglasov([True, True, False, True])
vrne3
.
Različica 1: preštejemo.
def glasov(obkrozeno):
obkrozenih = 0
for krozec in obkrozeno:
if krozec:
obkrozenih += 1
return obkrozenih
Različica 2: seštejemo. Če vemo, da je (v Pythonu in večini drugih jezikov) False
isto kot 0
in True
isto kot 1
, naloga sprašuje po vsoti seznama.
def glasov(obkrozeno):
return sum(obkrozeno)
Različica 3: znamo Python in povemo, da je glasov
isto kot sum
. Namesto, da bi pisali funkcijo, samo priredimo ime.
glasov = sum
- Napiši funkcijo
voljeni(obkrozeno, imena)
, ki prejme seznam obkroženih (kot v prejšnji nalogi) in seznam imen na glasovnici, ter vrne kandidatko, za katero je glasoval glasovalec. Če je obkroženih več imen ali pa nobeno, naj funkcija vrneNone
, saj glasovnica ni veljavna.
Če je na glasovnici obkroženo le eno ime, gremo čez pare iz obkrozeno
in imena
ter vrnemo ime, ki je obkroženo.
def voljeni(obkrozeno, imena):
if glasov(obkrozeno) == 1:
for krozec, ime in zip(obkrozeno, imena):
if krozec:
return ime
Malo drugačen razmislek: vrniti moramo ime, ki se nahaja na tistem mestu v imena
, na katerem se v obkrozeno
nahaja True
.
def voljeni(obkrozeno, imena):
if glasov(obkrozeno) == 1:
return imena[obkrozeno.index(True)]
Svaril sem pred uporabo metode index
, ker da vedno vrne indeks prvega elementa z iskano vrednostjo (kar morda ni točno tisto, kar hočemo) in ker je počasna, saj mora preiskati seznam, medtem ko bi z malo spretnosti indeks pridobili na kak drug način. Za ta primer svarili ne veljata. Vemo, da je True
le eden - to smo preverili v prejšnji vrstici - tako da index
vrne vrednost enega in edinega True
-ja. Poleg tega pa ni "počasen" oziroma ga ne moremo prav nič pospešiti, saj ne vemo, kje v obkrozeno
se nahaja True
. V resnici je druga funkcija bistveno hitrejša od prve, saj je Pythonova metoda indeks napisana v jeziku C, ki je pri takšnih rečeh 30-krat ali pa celo 100-krat hitrejši od Pythona.
- Napiši funkcijo
najvecji(d)
, ki kot argument prejme slovard
, poišče največjo vrednost in kot rezultat vrne par, ki vsebuje pripadajoči ključ in to vrednost. Klicnajvecji({"a": 5, "b": 9, "c": 8, "d": 4})
vrne("b", 9)
. Vrednosti so lahko tudi negativne!
To spominja na iskanje največjega elementa seznama, le da primerjamo eno reč, poleg nje pa vrnemo še eno drugo. To smo počeli že parkrat.
Tule gremo čez pare (ključ, vrednost) v slovarju d
(for k, v in d.items()
). Primerjamo vrednosti (v > naj_v
), zapomnimo pa si ključ in vrednost (naj_k, naj_v = k, v
).
Ker se v slovarju pojavljajo tudi negativne vrednosti, bomo začetno vrednost naj_k
in naj_v
nastavili na None
. Na ta način se lahko pogoj v zanki glasi "če nismo videli še nobene vrednosti ali pa je trenutna večja od največje doslej", to je if naj_v == None or v > naj_v
.
To poskrbi tudi za primer, ko je slovar prazen. V tem primeru se zanka nikoli ne izvede, naj_k
in naj_v
ostaneta None
, in funkcija vrne (None, None)
- tako kot mora.
def najvecji(d):
naj_k = naj_v = None
for k, v in d.items():
if naj_v == None or v > naj_v:
naj_k, naj_v = k, v
return naj_k, naj_v
- Napiši funkcijo
pristej(s, t)
, ki "prišteje" slovart
k slovarjus
. Če imamos = {"a": 5, "b": 6, "d": 4}
int = {"b": 3, "c": 8}
, klicpristej(s, t)
spremenis
v{"a": 5, "b": 9, "d": 4, "c": 8}
. Funkcija ne vrača ničesar.
Gremo čez slovar t
(for k in t
). Za vsak ključ k
preverimo, ali se nahaja v s
(if k in s
). Če se ne, prepišemo pripadajočo vrednost iz t
v s
(s[k] = t[k]
). Če se, pa vrednosti v slovarju s
prištejemo tisto iz t
(s[k] += t[k]
).
def pristej(s, t):
for k in t:
if k not in s:
s[k] = t[k]
else:
s[k] += t[k]
To je stvar okusa, vendar: kadar potrebujem ključe in vrednosti, grem raje čez items
. Takole:
def pristej(s, t):
for k, v in t.items():
if k not in s:
s[k] = v
else:
s[k] += v
Vendar prav v tem primeru nisem prepričan, da je to bolj elegantno. Pač pa postane bolj elegantno, ko se domislimo, da imajo slovarji metodo get
.
def pristej(s, t):
for k, v in t.items():
s[k] = s.get(k, 0) + v
V s[k]
zapišemo tisto, kar je bilo tam prej, pa še v
prištejemo zraven. Če v slovarju s
ni ključa k
, pa mora v
prišteti k ničli.
Napiši funkcijo
zmagovalec(glasovnice)
, ki prejme seznam glasovnic, na primer,[([False, True, True], ["Berrta", "Ana", "Cilka"]), ([False, True, True], ["Berrta", "Ana", "Cilka"]), ([False, False, True], ["Ana", "Cilka", "Berrta"]), ([False, True, False], ["Ana", "Berrta", "Cilka"]), ([False, True, False], ["Berrta", "Ana", "Cilka"]) ]
Vrniti mora ime kandidatke z največ glasovi in delež glasov zanjo na veljavnih glasovnicah. V gornjem primeru sta prvi dve glasovnici neveljavni. Zmagovalec je Berrta, ki je dobila dva glasova od treh, zato funkcija vrne
("Berrta", 2 / 3)
.
Pripravimo si slovar, v katerega bomo šteli glasove (glasovi = defaultdict(int)
). Uporabili bomo defaultdict(int)
, zato da bomo glasove za nove kandidate preprosto prištevali, ne da bi jih morali prej vstavljati v slovar; primere si lahko ogledate na predavanjih.
Nato gremo čez glasovnice (for obkrozeno, imena in glasovnice
). Za vsako pogledamo, kdo je obkrožen (obkrozen = voljeni(obkrozeno, imena)
) in če je glasovnica veljavna (if obkrozen != None
), k obkroženemu kandidatu prištejemo en glas (glasovi[obkrozen] += 1
). Nato uporabimo funkcijo najvecji
, da dobimo ime kandidata z največ glasovi in število glasov, ki jih je dobil.
Za konec moramo izvedeti še število veljavnih glasovnic, da bomo lahko vrnili delež dobljenih glasov. Ta je enak vsoti glasov, ki so jih dobili vsi kandidati skupaj; sešteti moramo vrednosti slovarja glasovi
(sum(glasovi.values())
)
def zmagovalec(glasovnice):
glasovi = defaultdict(int)
for obkrozeno, imena in glasovnice:
obkrozen = voljeni(obkrozeno, imena)
if obkrozen != None:
glasovi[obkrozen] += 1
zmago, glasov = najvecji(glasovi)
return zmago, glasov / sum(glasovi.values())
- Napiši funkcijo
voljeni_vec(obkrozeno, imena)
, ki prejme enake podatke kotvoljeni
, vendar obravnava vse glasovnice kot veljavne, število glasov pa razdeli med vse obkrožene kandidatke. Funkcija vrne slovar, katerega ključi so imena obkroženih kandidatk, vrednost pa 1 deljeno s številom obkroženih kandidatk. Če pokličemovabljeni_vec([True, False, True, True], ["Ana", "Berta", "Cilka", "Dani"])
, funkcija vrne{"Ana": 1 / 3, "Cilka": 1 / 3, "Dani": 1 / 3}
.
Določiti moramo delež glasu, ki ga dobi vsak obkroženi. Ta je enak 1 deljeno s število obkroženih, 1 / glasov(obkrozeno)
. Popaziti moramo še na glasovnice, na katerih ni obkrožen nihče. Tedaj bomo namesto s številom obkroženih delili z 1. Tako bo vsak od obkroženih dobil 1 glas, vendar ni obkroženih nihče, torej - komu mar. Se pravi, delez = 1 / (glasov(obkrozeno) or 1)
.
Zdaj pa pripravimo nov slovar, gremo prek glasovnice in vsakemu od obkroženih imen damo njegov delež glasu.
def voljeni_vec(obkrozeno, imena):
delez = 1 / (glasov(obkrozeno) or 1)
glasovi = {}
for krozec, ime in zip(obkrozeno, imena):
if krozec:
glasovi[ime] = delez
return glasovi
Dodatna naloga
Kandidatke seveda obljubijo to in ono. Recimo, da Ana obljubi cesto, šolo in kolesarko stezo. Nekdo si želi cesto, kolesarsko stezo, izboljšan javni prevoz in zastonj sladoled. Pokrivanje je 2 (cesta in steza), vseh stvari, ki so tu v igri, pa je 5 (cesta, šola, steza, prevoz, sladoled). Zato bomo rekli, da je ujemanje med Aninimi obljubami in temi željami 2 / 5, to je, 0.4.
- Napiši funkcijo
ujemanje(obljube, zelje)
, ki izračuna ujemanje, kot ga opisujemo v gornjem odstavku. Pri tem staobljube
inzelje
seznama nizov, na primer["cesta", "šola", "steza"]
.
Izračunati moramo dva seznama: skupne
bo vseboval vse skupne elemente seznamov obljube
in zelje
(po domače: presek), oboje
pa vse, ki se pojavijo na enem ali drugem ali obeh (po domače: unija).
def ujemanje(obljube, zelje):
skupne = []
oboje = obljube[:]
for zelja in zelje:
if zelja in obljube:
skupne.append(zelja)
else:
oboje.append(zelja)
return len(skupne) / (len(oboje) or 1)
Seznam skupne
bo v začetku prazen, v oboje
pa skopiramo vse elemente seznama obljube
. Nato gremo prek želja. Če je želja med obljubami, jo dodamo v presek. V uniji je tako ali tako že. Če želje ni med obljubami, pa je ne bomo dodali v presek, temveč manjka v uniji.
Na koncu vrnemo razmerje med dolžinama seznamov, s tem da k len(oboje)
pritaknemo še or 1
, tako da bomo v primeru, da je unija prazna, delili z 1. V slednjem primeru bo rezultat tako ali tako 0, saj je takrat, ko je unija prazna, presek prazen še toliko bolj.
Napiši funkcijo
naj_kandidata(kandidati, zelje)
. Ta prejme seznam parov (ime kandidata, obljube), na primer[("Ana", ["cesta", "šola", "steza"]), ("Berta", ["šola", "sladoled"]), ("Cilka", []), ("Dani", ["sladoled", "steza", "cesta", "šola"]) ]
in seznam želja, na primer
["cesta", "šola"]
. Funkcija vrne imeni kandidatov, ki se najbolj ujemata z željami - najprej prvega in nato drugega. Da ne bo prezapleteno, smete predpostaviti, da ne bo izenačenih mest.
Tale je pa malo daljša.
def naj_kandidata(kandidati, zelje):
ime1 = ujem1 = ime2 = ujem2 = None
for ime, obljube in kandidati:
ujem = ujemanje(obljube, zelje)
if ujem1 == None or ujem > ujem1:
ime2, ujem2 = ime1, ujem1
ime1, ujem1 = ime, ujem
elif ujem2 == None or ujem > ujem2:
ime2, ujem2 = ime, ujem
return ime1, ime2
Funkcija bo potrebovala imeni najboljših kandidatov doslej (ime1
in ime2
) ter ujemanje njihovih obljub z našimi željami (ujem1
in ujem2
). V začetku postavimo vse to na None
.
V zanki gremo prek kandidatov (ime
) in njihovih obljub (obljube
). Za vsakega izračunamo ujemanje (ujem
). Če je to prvi kandidat na seznamu (ujem1 == None
) ali pa je boljši od najboljšega doslej, potisnemo trenutnega vodilnega na drugo mesto (ime2, ujem2 = ime1, ujem1
) in si zapomnimo trenutno ime in ujemanje kot najboljše doslej (ime1, ujem1 = ime, ujem
). Sicer pa je trenutni kandidat morda boljši od drugega najboljšega (ali pa drugi najboljši še ne obstaja); v tem primeru si ga zapomnimo kot drugega najboljšega.
Ko je to končano, vrnemo imeni najboljših kandidatov.