Zapiski (2014/15)
Testi
Prva naloga, ki smo si jo zastavili, so bile "anadiploze". Priložnost smo izkoristili za to, da smo si ogledali, kako bodo videti testi, ki jih boste dobivali za domače naloge.
Test kliče vašo funkcijo z različnimi argumenti in preverja, ali naredi,
kar naj bi naredila. Tile testi so najpreprostejši: pričakujejo, da bo
funkcija vrnila True, če je pokličemo
anadiploza("barbar"), da bo vrnila True, če jo
pokličemo anadiploza("BB") ... in tako naprej, ter da bo vrnila
False, če jo pokličemo z anadiploza("Barbara") in
tako naprej.
Napišimo, za vajo, neumno rešitev: funkcijo, ki vrne True, če je
drugi znak besede "a".
Funkcijo napišemo nad teste. Teste najpreprosteje poženemo tako, da pustimo
kurzor izven testov in pritisnemo Ctrl-Shift-F10 (na Macu pa
Cmd-Shift-R). Če je kurzor znotraj testov, pa bo ta kombinacija pognala testno
funkcijo, v kateri je kurzor (tule imamo samo eno, test_anadiploza,
v domači nalogi pa so že tri, test_ena_luknja,
test_prek_luknje in test_nazaj).
Ko torej poženemo test, dobimo:
Oranžne "lučke" pomenijo, da program ne deluje pravilno. Rdeča bi pomenila, da se je sesul z napako. Zelena pomeni, da je vse v redu.
Ne pozabite pogledati sporočila o napaki. Takole pravi:
Iz predzadnjih dveh vrstic vidimo, na kakšen način funkcija ne deluje: ko
pokličemo anadiploza("BB") pričakujemo rezultat True,
funkcija pa vrne False, kar je narobe (False is not true).
Tole, zadnje sporočilo zveni malo smešno. Tako je, ker govori o "false" in
"true". Testi bodo pogosto izpisovali drugačna sporočila: imeli bomo, recimo,
funkcijo, od katere bomo pričakovali, recimo, seznam, [1, 2, 3].
Testirali bi jo, recimo, takole:
Torej, če pokličemo funkcijo vrni123 z argumentoma
arg1 in arg2, mora vrniti [1, 2, 3].
Druge "asserte" bomo spoznali sproti, ko jih bomo potrebovali.
Anadiploza
Anadiploza je beseda, sestavljene iz dveh ponovitev isti znakov, na primer, BARBAR ali PEPE, ne pa BARBARA ali PEPA.
Napisati je potrebno funkcijo, ki preveri, ali je podana beseda anadiploza ali ne. Hm, če je beseda dolga osem znakov, mora biti ničti enak četrtemu, prvi petemu, drugi šestemu in tretji sedmemu. Torej takole?
Pazite na dve stvari. Deljenje mora biti celoštevilsko: tudi 8 / 2 je 4.0,
torej necelo število, ki ga ne moremo uporabiti kot argument za
range ali za indeksiranje.
Drugo: return True je za zanko. Saj ta trik že poznate, ne?
Poženemo teste in izvemo
Eh, OK, "B" ni enak "b"-ju. Spremenimo celo besedo v velike črke, pa bo.
Pa bo?! Pa ni.
Hm, zakaj ga tisti "a" na koncu ne zmoti?!
Zgodi se tole: 7 // 2 je 3. Primer 0 in 3, 1 in 4, 2 in 5. Zadnje pa ne pogleda. Še najboljši način, da uredimo še to, je, da v začetku preverimo, ali je beseda sode dolžine.
Takole bi to rešili v večini računalniških jezikov; vsaj v Cju in Pascalu prav gotovo. V Pythonu pridemo skozi ceneje: Python zna početi precej stvari z nizi in seznami. Recimo vzeti prvih ali zadnjih toliko in toliko črk.
Tako pridemo do veliko preprostejše funkcije: povedati mora, ali je prvih
len(beseda) // 2 znakov enakih znakom od teh znakov naprej. Če je
dolžina liha, bo drugih pač več in podniza ne bosta enaka.
Ne pišite, lepo prosim, tega.
Izraz beseda[:len(beseda) // 2] == beseda[len(beseda) // 2:] že
ima vrednost True ali False - vrednost tega izraza je
že prav tisto, kar si želimo vrniti. Njegova vrednost je že
True ali False.
Še malo bolj neumno je pisati
Saj --- vi ste za take kozlarije prepametni, OK?
Deljenje besed
Malenkost sorodna naloga z nekega drugega izpita je takšna. Če niz
"Bar" pomnožimo s 3, dobimo BarBarBar. Napišimo
funkcijo deli, ki deli podani niz s podanim številom. Tako mora,
recimo, deli("BarBarBar", 3) vrniti "Bar". Če niz ni
"deljiv" (recimo, da poskušamo "BarBarBar" deliti z 2), naj funkcija vrne
None.
Tudi tole lahko precej zapletemo. Vrniti moramo prvih
len(beseda) // n znakov niza, torej
beseda[:len(beseda) // n]. Precej bolj zoprna reč
(ali pa tudi ne?) je preveriti, ali je niz res sestavljen iz n
ponovitev teh znakov.
Če si malo napišemo, pridemo to tega:
prvi je prvih len(beseda) // n znakov besede.
Enaki mu morajo biti kosi beseda[i * dol:i * dol + dol], pri čemer
gre i od 0 do n - 1.
Pa še to ne deluje povsem: če bi delili BARBARA z 2, bo funkcija (podobno kot prej anadiploze) spregledala odvečni A na koncu.
Obstaja veliko preprostejša rešitev. Vzamemo prvih dol znakov
in jih pomnožimo z n. Če nam to da začetno besedo, se je deljenje
izšlo, sicer ne. V slednjem primeru ne vrnemo ničesar, kar je isto, ko če bi
vrnili None.
Naključna delitev otrok
Napisati je potrebno funkcijo, ki dobi seznam imen otrok in velikost skupine. Vrniti mora seznam naključno sestavljenih skupin otrok (torej seznam seznamov imen). Če se deljenje ne izide, bo zadnja skupina manjša.
Naredimo lahko, recimo, tole.
skupine bo seznam seznamov, ki ga bomo vrnili, in
v t bomo sestavljali skupine. V začetku sta oba prazna.
Nato ponavljamo tole. Naključno izberemo enega učenca s seznama
(en = random.choice(ucenci)). Dodamo ga v skupino in pobrišemo
iz seznama (t.append(en) in ucenci.remove(en)). Če
je skupina s tem že dovolj velika, jo dodamo v seznam skupin
(skupine.append(t)) in začnemo z novo, prazno skupino
(t = []). Vse skupaj ponavljamo, dokler seznama z učenci ne
izpraznimo (while ucenci).
Če se deljenje ni izšlo, je ostalo nekaj učencev v manjši, zadnji skupini.
Če po koncu zanke skupina (t) ni prazna (if t:),
dodamo v seznam skupin še to skupino.
Taka rešitev naj bo za zdaj sprejemljiva, kasneje pa se bom nad takšnimi
rečmi pritoževal. Funkcija namreč spreminja seznam učencev, ki ga je dobila
kot argument. Ta, ki je poklical funkcijo, najbrž ne pričakuje, da mu bo
funkcija uničila podani seznam (kaj, če ga še za kaj potrebuje?). To lahko
popravimo tako, da na začetek funkcije dodamo ucenci = ucenci[:]
ali ucenci = ucenci.copy().
Vse skupaj gre tudi preprosteje. Učence najprej naključno premešamo. Nato
jemljemo po n učencev z
skupine.append(ucenci[i:i + n]), pri čemer gre i od
začetka do konca s korakom n,
for i in range(0, len(ucenci), n).
Nekega lepega dne se bomo naučili, da gre še preprosteje: