Kaladont na hitro
Spomnimo se naloge Kaladont. Po par tednih smo toliko pametnejši, da znamo z uporabo slovarjev, množic, izpeljanih slovarjev, generatorjev in različnih drugih trikov večino funkcij napisati veliko krajše. Točneje, vse te funkcije je možno napisati tako, da vsebujejo en sam return
in nič drugega.
V nalogi tudi ne smeš definirati nobenih drugih funkcij kot te, ki jih zahteva naloga.
Testi
Testi: testi-kaladont-na-hitro.py
Obvezna naloga
Napiši enovrstične različice funkcij
lahko_sledi(prej, potem)
ni_ponavljanj(besede)
preveri_zaporedje(besede)
mozne_naslednje(beseda, slovar)
Rešitev
Kot so nekateri opazili, sem rešitve te naloge že praktično objavil skupaj z (daljšimi) rešitvami prvotne naloga Kaladont. Razmišljal sem, da s tem ni nič narobe, če to pomeni, da bo rešitve naloge prebral kdo, ki tega sicer ne bi storil. In če ga bo to spodbudilo k temu, da bo to počel tudi v prihodnje.
def lahko_sledi(prej, potem):
return prej[-1] == potem[0] and prej != potem
def ni_ponavljanj(besede):
return len(set(besede)) == len(besede)
def preveri_zaporedje(besede):
return ni_ponavljanj(besede) and \
all(lahko_sledi(prej, potem) for prej, potem in zip(besede, besede[1:]))
def mozne_naslednje(beseda, slovar):
return [potem for potem in slovar if lahko_sledi(beseda, potem)]
Funkcija lahko_sledi
ni nič takšnega, česar ne bi morali znati že, odkar znamo napisati funkcijo.
Pri ni_ponavljanj
so se nekateri spomnili na množice, drugi so pisali reči v slogu return all(besede.count(beseda) == 1 for beseda in besede)
. Tudi prav; so pač ponovili count
in se spomnili na all
...
... ki nam pride prav pri preverjanju zaporedja. Tu očitno najprej pokličemo funkcijo ni_ponavljanj
, nato pa preverimo, ali za vsak par besed prej
in potem
(for prej, potem in zip(besede, besede[1:])
) velja, da lahko_sledi(prej, potem)
.
Tisti, ki se niso spomnili na all
, so navadno sestavili seznam True
jev in False
ov, lahko_sledi(prej, potem) for prej, potem in zip(besede, besede[1:])
, potem pa niso vedeli, kaj z njim. Lahko ga uporabimo, recimo, tako
def preveri_zaporedje(besede):
return ni_ponavljanj(besede) and \
False not in [lahko_sledi(prej, potem) for prej, potem in zip(besede, besede[1:])]
Deluje, ampak z all
je lepše.
O mozne_naslednje
pa se spet nimamo kaj dosti pogovarjati.
Dodatna naloga
Napiši enovrstične različice funkcij
izberi_besedo(beseda, slovar)
Tu iščemo besedo, ki je v določenem smislu najboljša, torej minimalna ali maksimalna po določenem kriteriju. Preuči dokumentacijo za funkciji max in min. Za lažje razumevanje lahko pogledaš še nekaj o urejanju, vendar ne uporabljaj funkcije
sort
, saj je ne potrebuješ.Pri programiranju smeš uporabiti funkcijo
kvaliteta_besede
, ki se nahaja v testih. Dobro si jo oglej in jo razumi.pogostosti_zacetnic(slovar)
Od tvoje rešitve ne pričakujemo, da bo zelo hitra (seveda pa se bo tudi slaba funkcija pri tako kratkih testih izvedla v trenutku).
Rešitev
Kdor je reševal izberi_besedo
in pri tem uporabil kvaliteta_besede
, se je naučil, kako urejati ali pa izbirati največji oz. najmanjši element po več kot enem kriteriju.
Funkcija max
vrne največji element seznama. Seznam lahko vsebuje karkoli, kar je mogoče primerjati. Če vsebuje nize, bo vrnil tistega, ki je največji - po kriteriju, po katerem operator <
primerja nize. To seveda ni dolžina (čemu bi to služilo?), temveč operator <
primerja nize po abecedi. Tako bi funkcija max(slovar)
bi vrnila besedo v slovarju, ki je zadnja po abecedi.
Kaj pa, če bi hoteli poiskati najdaljšo besedo v slovarju? Za take potrebe ima max
dodaten argument, ki ga moramo podati z imenom. Poklicati moramo max(slovar, key=len)
. len
je, vemo, funkcija, ki vrne dolžino niza. Funkcija max
bo za vsak element seznama poklicala podano funkcijo len
in vrnila tisti element, pri katerem len
vrne največjo vrednost.
Pa če bi hoteli poiskati besedo z največ a-ji? Funkcije, ki bi vrnila število a
-jev v besedi, ni, seveda pa jo lahko napišemo.
def stevilo_ajev(beseda):
return beseda.count("A")
print(max(slovar, key=stevilo_ajev)
Če bi hoteli funkcijo z največ e-ji, pišemo, seveda,
def stevilo_ejev(beseda):
return beseda.count("E")
print(max(slovar, key=stevilo_ejev)
Pa če hočemo tako z največ a-ji, a ker je besed z enakim (največjim) številom a-jev več, bomo med njimi izbrali tisto z največ e-ji? Tu si pomagamo s terkami in tem, kako Python primerja terke: dve terki primerja po prvem elementu, če je ta enak, pa po drugem (in če bi bil enak tudi ta, po tretjem, in tako naprej).
def stevilo_ajev_in_ejev(beseda):
return (beseda.count("A"), beseda.count("E"))
print(max(slovar, key=stevilo_ajev_in_ejev)
Kaj pa, če bi med besedami z enakim številom a-jev hoteli tisto z najmanj e-ji? Zdaj imamo problem, saj kličemo funkcijo max
, ne min
-- saj hočemo največ a-jev.
Problem rešimo z malo zvitosti
def stevilo_ajev_in_ejev(beseda):
return (beseda.count("A"), -beseda.count("E"))
Pred beseda.count("E")
dodamo minus, pa bo iskal tistega z največ minus E-ji, se pravi tistega z najmanj E-ji.
Naloga zahteva, da poiščemo besedo, ki je najdaljša, med enako dolgimi pa tisto, ki je prej po abecedi. Če bi imeli funkcijo
def kvaliteta_besede(beseda):
return (len(beseda), beseda)
in z njo poklicali max
, bi dobili najdaljšo besedo, med njimi pa tisto, ki je zadnja po abecedi. Problema ne moremo rešiti tako kot zgoraj, kjer smo negirali število E-jev. beseda
je niz in je ne moremo "negirati". Zato trik obrnemo. Iščemo minimum, funkcija, s katero si pomagamo, pa ima negirano dolžino -- to je natančno funkcija, ki je bila priložena navodilom (oz. testom):
def kvaliteta_besede(beseda):
return (-len(beseda), beseda)
Rešitev naloge je torej
def izberi_besedo(beseda, slovar):
return min(mozne_naslednje(beseda, slovar), key=kvaliteta_besede)
Če se ne gremo teh trikov s key
je rešitev malo grša.
def izberi_besedo(beseda, slovar):
return min(mozna for mozna in mozne_naslednje(beseda, slovar)
if len(mozna) == max(len(b) for b in mozne_naslednje(beseda, slovar)))
Poiščemo prvo besedo po abecedi min
med tistimi besedami izmed možnih naslednjih (for mozna in mozne_naslednje(beseda, slovar)
), ki so dolge toliko, kolikor je daljša najdaljša beseda izmed možnih naslednjih (if len(mozna) == max(len(b) for b in mozne_naslednje(beseda, slovar))
). Ta funkcija je seveda grozna: za vsako besedo na novo išče dolžino najdaljše besede, pri čemer mora še vsakič na novo gledati, katere so možne naslednje besede.
Ekvivalentna rešitev je
def izberi_besedo(beseda, slovar):
kandidati = mozne_naslednje(beseda, slovar)
naj_dolzina = max(len(kandidat) for kandidat in kandidati)
return min(mozna for mozna in kandidati if len(mozna) == naj_dolzina)
Z njo ni nič zelo narobe, le testov ne prestane, ker zahtevajo, da vse opravimo v eni vrstici in ne treh. A če boste programirali tako, vam ne bo nihče zameril.
Tudi pogostosti začetnic so vam dale vetra -- in to poučnega. Veliko vas je poskušalo najprej sestavite seznam in ga potem nekako spreminjati. A čim napišemo [0] * 25
(ali, hujše, [0, 0, 0, 0, ... in tako naprej]
), smo dobili seznam petindvajsetih ničel, ki jih ne bo nihče več spremenil v kaj drugega. V tovrstnih programih pozabite na +=
, tega ni. Ti programi delajo le s konstantami in ne morejo ničesar spreminjati.
Enako so se zapletli tisti, ki so si predstavljali, da bo potrebno iti čez slovar. Čim napišemo [... for beseda in slovar]
bomo dobili seznam, ki ima toliko elementov, kolikor je v slovarju besed.
Razmišljati moramo začeti s prave strani. Če želimo slovar s 25 elementi, ki bodo ustrezali črkam slovenska abecede, potrebujemo zanko čez 25 stvari, to je, čez 25 črk slovenske abecede. Torej [... for c in "ABCČDEFGHIJKLMNOPRSŠTUVZŽ"]
Zdaj pa moramo ...
zamenjati z nekim izrazom, ki bo preštel, koliko besed se začne s črko c
. Seznam takšnih besed menda znamo sestaviti: [beseda for beseda in slovar if beseda[0] == c]
. Dolžina tega seznama pa je prav to, kar iščemo.
def pogostosti_zacetnic(slovar):
return [len([beseda for beseda in slovar if beseda[0] == c])
for c in "ABCČDEFGHIJKLMNOPRSŠTUVZŽ"]
Lahko pa se spomnimo, da je True
isto kot 1
in False
isto kot 0
. Sestavimo seznam True
-jev in False
-ov, ki bodo povedali ali se beseda v slovarju začne s pravo črko ali ne. Nato ga seštejemo.
def pogostosti_zacetnic(slovar):
return [sum(beseda[0] == c for beseda in slovar)
for c in "ABCČDEFGHIJKLMNOPRSŠTUVZŽ"]