Testi

Testi: testi-filtri.py

Naloga

V tej nalogi bomo programirali filtre, ki kot argument dobijo seznam in kot rezultat vrnejo spremenjen seznam. Primer takšnega filtra je filter Pristej. Ko ga skonstruiramo, povemo, koliko naj prišteva. Ko ga pokličemo in mu podamo seznam kot argument, vrne nov seznam, v katerem je vsakemu elementu prišteto, kolikor smo hoteli.

>>> f = Pristej(2) >>> f([1, 7, 2]) [3, 9, 4] >>> f([0, 0, 12]) [2, 2, 14] >>> f([]) []

Podoben filter je Pomnozi, ki namesto prištevanja množi.

Filter Obrni obrne podani seznam.

>>> t = Obrni() >>> t([1, 2, 3]) [3, 2, 1]

Filter Normaliziraj deli vse elemente seznama z največjim elementom. (Dodatno, naknadno pojasnilo: če je največji element seznama enak 0, seznama ne spreminjamo. Predpostaviti smemo tudi, da v seznamu ni negativnih elementov.)

>>> n = Normaliziraj() >>> n([5, 4, 1, 8, 3]) [0.625, 0.5, 0.125, 1.0, 0.375]

V vseh gornjih primerih smo filter poklicali - kot da bi bil funkcija. Tule je še en primer.

>>> s = [1, 2, 4] >>> v = n(s) >>> v [0.25, 0.5, 1.0]

Filtri pa imajo še eno metodo: obdelaj. Ta ne vrača novega seznama, temveč spreminja obstoječega.

>>> t.obdelaj(s) >>> s [0.25, 0.5, 1.0]

Poleg tega imajo filtri tudi metodo opis, ki vrne (ne izpiše!) opis filtra.

>>> f.opis() 'Pristej 2' >>> t.opis() 'Obrni' >>> n.opis() 'Normaliziraj' >>> Pomnozi(42).opis() 'Pomnozi z 42'

Končno, filtri imajo tudi metodo izpisi. Ta naredi nekaj podobnega, kot če pokličemo seznam, le da vrne terko, ki vsebuje novi seznam in opis spremembe.

>>> f.izpisi([4, 6, 8]) ([6, 8, 10], 'Pristej 2: [4, 6, 8] -> [6, 8, 10]')

Vse skupaj je narejeno takole. Vsi filtri so izpeljani iz razreda Filter:

class Filter: def izpisi(self, s): n = self(s) return n, "{}: {} -> {}".format(self.opis(), s, n) def __call__(self, s): return self.akcija(s) def obdelaj(self, s): s[:] = self.akcija(s)

Ta razred je takšen, kot je in ga ne smete spreminjati! Razred definira metodo __call__; ta poskrbi, da je filtre možno poklicati ("kot da bi bili funkcije": objekte razredov, ki imajo metodo __call__, lahko pokličemo). Poleg tega definira metodo obdelaj, ki prav tako naredi, kar mora - spreminja seznam. Obe pa kličeta metodo akcija. Te metoda pa ... ni!

Podobno metoda izpisi kliče metodo akcija, poleg nje pa še opis, ki ga prav tako ni.

Ogrevalna naloga

Obleci tople nogavice in popij skodelico čaja, po možnosti božičnega ali pa žajbljevega z medom.

Pitja kuhanega vina pred programiranjem ne priporočam, saj po mojih izkušnjah že najmanjša količina zaužitega alkohola (vključno s čajem z rumom) bistveno poveča število trivialnih napak. (Ne nujno: http://imgs.xkcd.com/comics/ballmer_peak.png; hvala, Tejo Ličen!)

Obvezna naloga

Sprogramiraj razrede Pristej, Pomnozi, Obrni in Normaliziraj. Vsi naj bodo izpeljani iz Filter. Vsak razred naj definira metodi akcija in opis. Kjer je potrebno, definiraj še konstruktor __init__. Dodajanje drugih metod je prepovedano! Spreminjanje metod obdelaj, __call__ ali izpisi je še bolj prepovedano. Spreminjanje razreda Filter je najbolj prepovedano.

Nize, ki jih mora vračati opis, si lahko ogledate zgoraj.

Metoda akcija naj sprejme seznam in vrne nov, "filtrirani" seznam. V bistvu akcija naredi tisto, kar se zgodi, ko pokličemo filter.

Dodatna naloga

Sprogramiraj funkcije (ne metode!) izvedi, obdelaj in preveri.

Recimo, da imamo seznam filtrov in seznam, ki bi ga radi spustili prek teh filtrov.

>>> filtri = [Pomnozi(2), Pristej(-1), Obrni(), Pomnozi(0.5)] >>> s = [3, 7, 1, 2, 4]

Funkcija izvedi dobi kot argument seznam filtrov in seznam. Vrne prefiltriran seznam - seznam, ki gre prek vseh naštetih filtrov.

>>> izvedi(filtri, s) [3.5, 1.5, 0.5, 6.5, 2.5]

Funkcija preveri je podobna, le da namesto seznama vrne terko: novi seznam in opis poteka.

t, o = preveri(filtri, s) >>> t [3.5, 1.5, 0.5, 6.5, 2.5] >>> print(o) Pomnozi z 2: [3, 7, 1, 2, 4] -> [6, 14, 2, 4, 8] Pristej -1: [6, 14, 2, 4, 8] -> [5, 13, 1, 3, 7] Obrni: [5, 13, 1, 3, 7] -> [7, 3, 1, 13, 5] Pomnozi z 0.5: [7, 3, 1, 13, 5] -> [3.5, 1.5, 0.5, 6.5, 2.5]

Funkcija obdelaj je podobna funkciji izvedi, le da ne vrača novega seznama temveč spreminja obstoječega.

Rešitev

Obvezna naloga

Da rešimo nalogo, moramo narediti le, kar pravi naloga: vsakemu razredu dodamo metodi akcija in opis. Metode same po sebi so tako preproste, da me skrbi, če zaradi njihove trivialnosti niste bili nekoliko užaljeni.

Poleg tega pa nekateri razredi potrebujejo konstruktor. Kateri? Tisti, ki si morajo kaj zapomniti, shraniti pri sebi kaj takšnega, kar potrebujejo za delovanje. Filtra Obrni in Normaliziraj nista takšna: vsako zaporedje, ki ga dobita, pač obrneta oziroma normalizirata. Filtra Pristej in Pomnozi pa morata vedeti, s čim naj pomnožita podano zaporedje. To ni parameter, ki bi ga podali ob klicu filtra (ali klicu metode akcija), temveč ga podamo konstruktorju in filter si ga zapomni za vedno. Tadva torej potrebujeta konstruktor, ki bo nekam v objekt (self) zapisal vrednost podanega argumenta.

# Razred Filter je bil podan in ga ne smemo spreminjati! class Filter: def izpisi(self, s): n = self(s) return n, "{}: {} -> {}".format(self.opis(), s, n) def __call__(self, s): return self.akcija(s) def obdelaj(self, s): s[:] = self.akcija(s) class Obrni(Filter): def opis(self): return "Obrni" def akcija(self, s): return list(reversed(s)) class Pristej(Filter): def __init__(self, a): self.a = a def opis(self): return "Pristej {}".format(self.a) def akcija(self, s): n = [] for e in s: n.append(e + self.a) return n class Pomnozi(Filter): def __init__(self, f): self.f = f def opis(self): return "Pomnozi z {}".format(self.f) def akcija(self, s): n = [] for e in s: n.append(e * self.f) return n class Normaliziraj(Filter): def opis(self): return "Normaliziraj" def akcija(self, s): if not s: return [] m = max(s) if m == 0: return s[:] n = [] for e in s: n.append(e / m) return n

Dodatna naloga

Dodatna naloga zahteva le, da po vrsti pokličemo vse filtre iz seznama.

def izvedi(filtri, s): for filter in filtri: s = filter(s) return s def obdelaj(filtri, s): for filter in filtri: filter.obdelaj(s) def preveri(filtri, s): res = "" for filter in filtri: s, t = filter.izpisi(s) res += t + "\n" return s, res
Zadnja sprememba: četrtek, 25. marec 2021, 21.34