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.

import unittest class TestAnadiploza(unittest.TestCase): def test_anadiploza(self): self.assertTrue(anadiploza("barbar")) self.assertTrue(anadiploza("BB")) self.assertTrue(anadiploza("Barbar")) self.assertTrue(anadiploza("aaaa")) self.assertFalse(anadiploza("Barbara")) self.assertFalse(anadiploza("B")) self.assertFalse(anadiploza("aaaaa"))

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".

def anadiploza(beseda): return beseda[1] == "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:

====================================================================== FAIL: test_anadiploza (ana.TestAnadiploza) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/janezdemsar/Dropbox/Pedagosko/PeF - P1/p01/ana.py", line 20, in test_anadiploza self.assertTrue(anadiploza("BB")) AssertionError: False is not true

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:

self.assertEqual(vrni123(arg1, arg2), [1, 2, 3])

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?

def anadiploza(beseda): pol = len(beseda) // 2 for i in range(pol): if beseda[i] != beseda[pol + i]: return False return True

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

self.assertTrue(anadiploza("Barbar")) AssertionError: False is not true

Eh, OK, "B" ni enak "b"-ju. Spremenimo celo besedo v velike črke, pa bo.

def anadiploza(beseda): beseda = beseda.upper() pol = len(beseda) // 2 for i in range(pol): if beseda[i] != beseda[pol + i]: return False return True

Pa bo?! Pa ni.

self.assertFalse(anadiploza("Barbara")) AssertionError: True is not false

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.

def anadiploza(beseda): if len(beseda) % 2 == 1: return False beseda = beseda.upper() pol = len(beseda) // 2 for i in range(pol): if beseda[i] != beseda[pol + i]: return False return True

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.

def anadiploza(beseda): beseda = beseda.lower() return beseda[:len(beseda) // 2] == beseda[len(beseda) // 2:]

Ne pišite, lepo prosim, tega.

def anadiploza(beseda): beseda = beseda.lower() if beseda[:len(beseda) // 2] == beseda[len(beseda) // 2:]: return True else: return False

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

def anadiploza(beseda): beseda = beseda.lower() if (beseda[:len(beseda) // 2] == beseda[len(beseda) // 2:]) == True: return True else: return False

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:

def deli(beseda, n): dol = len(beseda) // n prvi = beseda[:dol] for i in range(n): if beseda[i * dol:i * dol + dol] != prvi: return None return beseda[:len(s) // n]

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.

def deli(s, n): prvi = s[:len(s) // n] if prvi * n == s: return prvi

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.

import random def razdeli_nakljucno(ucenci, n): skupine = [] t = [] while ucenci: en = random.choice(ucenci) t.append(en) ucenci.remove(en) if len(t) == n: skupine.append(t) t = [] if t: skupine.append(t) return skupine

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).

def razdeli_nakljucno(ucenci, n): random.shuffle(ucenci) skupine = [] for i in range(0, len(ucenci), n): skupine.append(ucenci[i:i + n]) return skupine

Nekega lepega dne se bomo naučili, da gre še preprosteje:

def razdeli_nakljucno(ucenci, n): random.shuffle(ucenci) return [ucenci[i:i + n] for i in range(0, len(ucenci), n)]
Last modified: Thursday, 5 October 2017, 11:46 AM