Deljenje

Kot boste spoznali tudi v vlogi učitelja, je včasih težko uganiti, katera naloga bo za učence lahka in katera težka. Deljenje je lep primer. Naloga je zahtevala, da napišete program, ki dela natančno isto, kot počnete, ko pisno delite dve števili. Prav zato se mi je zdelo, da bo posrečena za študente Pedagoške fakultete (pri računalnikarji bi me bilo strah za njihovo zmožnost pisnega deljenja - če sem malo hudoben).

Kar počnemo, ko pisno delimo, je tole:

def deli(a, b): s = "0." for i in range(50): a *= 10 s += str(a // b) a %= b return s

Ker vemo, da je a manjši od b, bomo začeli z "0.". Nato v vsakem koraku pomnožimo a z 10, pogledamo, kolikokrat "gre b v a" in to dodamo v niz (s += str(a // b)) v a pa ostane, kar pač ostane (a %= b).

Pri tej nalogi sem pričakoval, da bo večina dobila poceni 20%, vendar se to ni zgodilo. Zakaj, ne vem - ugibam, da preprosto niste znali povezati pisnega deljenja z "algoritmom".

Prednik

Tule gre za precej običajno rekurzijo.

Če je oseba otrok prvega, potem je prednik, ki ga iščemo, kar prvi.

Sicer vprašamo vsakega otroka, kdo je prednik osebe (p = prednik(otrok, oseba)). Če otrok vrne drugega kot None (pravzaprav bi lahko pisali tudi kar if p:, vrnemo to reč.

Če funkcija na najde ničesar, tudi ne vrne ničesar, torej vrne None, kot zahteva naloga.

def prednik(prvi, oseba): if oseba in otroci[prvi]: return prvi for otrok in otroci[prvi]: p = prednik(otrok, oseba) if p is not None: return p

Zaporedni bomboni

Malenkost napačna rešitev je takšna:

# Ta rešitev ni povsem pravilna! def vec_zapored(imena): zaporedni = set() for i in range(len(imena)): if imena[i] == imena[i - 1]: zaporedni.add(imena[i]) return zaporedni

Gremo prek seznama, za vsakega preverimo, ali je enak prejšnjemu in ga, če je tako, dodamo v množico.

Rešitev ne deluje pravilno, ko je i enak 0, saj tedaj primerja elementa z indeksoma 0 in -1, torej prvi in zadnji element seznama. To lahko rešimo tako, da začnemo pri prvem, ne ničtem.

def vec_zapored(imena): zaporedni = set() for i in range(1, len(imena)): if imena[i] == imena[i - 1]: zaporedni.add(imena[i]) return zaporedni

Mimogrede, bo kaj narobe, če en in isti element dodamo večkrat? Ne, tole so množice, torej večkratno dodajanje istega elementa nima učinka. Vsak element je v množici le enkrat.

Na predavanjih sem često protestiral proti range(len(nekaj)). No, dovolil sem ga, kadar v resnici potrebujemo indeks in nam enumerate ne bi zadoščal. A tudi tu je lepše, če uporabimo zadrgo na način, ki smo ga spoznali tudi na predavanjih.

def vec_zapored(imena): zaporedni = set() for ime1, ime2 in zip(imena, imena[1:]): if ime1 == ime2: zaporedni.add(ime1) return zaporedni

Klic zip(imena, imena[1:]) sestavi seznam zaporednih parov in potem je naša naloga preprosta.

Ko pridemo do sem, pa pravzaprav znamo obrniti funkcijo v učinkovitejšo rešitev v eni vrstici:

def vec_zapored(imena): return {e1 for e1, e2 in zip(imena, imena[1:]) if e1 == e2}

Posebnež

Ta naloga je lepa, ker ima toliko različnih rešitev. Moja je bila takšna.

import collections def posebnez(s): koliko_kdo = collections.defaultdict(list) for otrok, bombonov in s.items(): koliko_kdo[bombonov].append(otrok) for kdo in koliko_kdo.values(): if len(kdo) == 1: return kdo[0]

Pripravimo slovar koliko_kdo, katerega ključi so različna števila bombonov, vrednosti pa seznam otrok, ki ima to število bombonov. Če imajo, recimo, Ana, Berta in Cilka po tri bonbone, Dani pa štiri, bomo dobili slovar {3: ["Ana", "Berta", "Cilka"], 4: ["Dani"]}.

V drugi zanki se sprehodimo čez vrednosti v tem slovarju - ključi nas niti ne zanimajo, potrebovali smo jih le, ko smo slovar sestavljali. Pravzaprav vemo, kako bodo videti: ena vrednost bo seznam vseh otrok razen enega, druga vrednost pa seznam s tem otrokom. Poiščemo torej seznam z enim samim elementom in vrnemo prvi (in seveda edini) element tega slovarja.

Zanimiva povsem drugačna rešitev je tale: sestavimo seznam parov (stevilo_bombonov, ime_otroka) in ga uredimo. Posebnež bo zdaj prvi ali zadnji - glede na to, ali ima več ali manj bombonov kot ostali. Če imata prva dva otroka enako bombonov, vrnemo ime zadnjega, sicer ime prvega.

def posebnez(s): po_bombonih = [(bombon, otrok) for otrok, bombon in s.items()] po_bombonih.sort() if po_bombonih[0][0] == po_bombonih[1][0]: return po_bombonih[-1][1] else: return po_bombonih[0][1]

Program je malo zoprn zaradi igre indeksov. Podobnih rešitev je še veliko. Lahko vzamemo, recimo, tri otroke in preverimo, koliko bombonov imajo. Če je eden različen, vrnemo njegovo ime. Če jih imajo vsi trije enako, gremo čez slovar in poiščemo otroka, ki jih nima toliko...

Evidenca bombonov

Število bombonov bo najpreprosteje shranjevati v defaultdict(int), katerega ključi bodo imena, vrednosti število bombonov.

Metode so zdaj preproste.

import collections class Otroci: def __init__(self): self.bomboni = collections.defaultdict(int) def daj_bombone(self, otrok, koliko): self.bomboni[otrok] += koliko def koliko(self, otrok): return self.bomboni[otrok] def skupno_bombonov(self): return sum(self.bomboni.values()) def posebnez(self): return posebnez(self.bomboni)

Ne spreglejte, kako lepo smo izračunali vsoto - gre le za vsoto vrednosti slovarja self.bomboni. Za metodo smo poklicali funkcijo posebnez iz prejsnje naloge, kot argument pa je dobila slovar.

Zadnja sprememba: četrtek, 27. avgust 2015, 18.40