Rim (nadaljevanje naloge Nim)
Rekurzivni nim - Rim
Napisali boste nekaj funkcij, ki ste jih pisali za nalogo Nim - le da bodo tokrat rekurzivne.
Testi
Testi: testi-rim.py
Obvezna naloga
Napiši naslednje funkcije:
- vsota(s)vrne vsoto števil v podanem seznamu. To seveda že poznamo s predavanj, vendar vseeno sprogramiraj še enkrat. Sam(a). Vsoto, vemo, izračunamo tako, da k prvemu elementu prištejemo vsoto ostalih.
- vsebuje(s, x)vrne- True, če seznam- svsebuje element- xin- False, če ga ne. Stvar je preprosta: prazen seznam očitno ne vsebuje- x. Sicer pa preverimo, ali je prvi element enak- x, ali pa je- xvsebovan v preostanku seznama.
- najvecja_potenca(n)vrne največjo potenco števila 2, ki je manjša ali enaka podanemu števila- n. Klic- najvecja_potenca(22)vrne- 16, saj bi bilo- 32že preveč. Klic- najvecja_potenca(32)pa vrne- 32. Predpostaviti smeš, da je- npozitivno celo število.- Kako to narediti rekurzivno? Preprosto: največja potenca, ki gre v - n, je dvakrat večja od največje potence, ki gre v- npolovic (če uporabljamo celoštevilsko deljenje).- Je to res? - največja potenca, ki gre v 22 je dvakrat večja od največje potence, ki gre v 11;
- največja potenca, ki gre v 11, je dvakrat večja od največje potence, ki gre v 5;
- največja potenca, ki gre v 5, je dvakrat večja od največje potence, ki gre v 2;
- največja, potenca, ki gre v 2, je dvakrat večja od največje potence, ki gre v 1;
- največja potenca, ki gre v 1, je 1; (... torej je največja potenca, ki gre v 2, 2; torej gre v 5 4, torej gre v 11 8, torej gre v 22 16).
 
Rešitev
Vsota števil v seznamu je enaka vrednosti prvega elementa, ki ji prištejemo vsoto ostalih elementov. Reč se ustavi, ko je seznam prazen; vsota elementov v njem je 0.
def vsota(s):
    if not s:
        return 0
    else:
        return s[0] + vsota(s[1:])
Rokohitrsko, zavedajoč se, da je False isto kot 0:
def vsota(s):
    return s != [] and s[0] + vsota(s[1:])
Seznam s vsebuje x, če ni prazen in je x prvi element ali pa ostanek vsebuje x. Reč se ustavi, ko je seznam prazen in Python ne gre preverjat ostanka pogoja.
def vsebuje(s, x):
    return s != [] and (s[0] == x or vsebuje(s[1:], x))
Z največjo potenco pa opravimo, kot svetuje naloga: "največja potenca, ki gre v n, je dvakrat večja od največje potence, ki gre v n polovic (če uporabljamo celoštevilsko deljenje)." Reč se ustavi, če je n enak 1; tedaj je največja potenca enaka 1.
def najvecja_potenca(n):
    if n == 1:
        return 1
    return 2 * najvecja_potenca(n // 2)
Dodatna naloga
- razbij(n)vrne seznam potenc števila 2, ki se seštejejo v podano število- n; vsaka potenca lahko nastopa le enkrat. Klic- razbij(22)vrne- [16, 4, 2]. Števila naj bodo urejena v padajočem vrstnem redu. Nalogo reši tako, da najprej s prejšnjo funkcijo poiščeš največjo potenco. Nato poiščeš seznam, ki predstavlja razbitje ostanka, in mu (spredaj) dodaš to potenco.
- brez(s, x)prejme nek seznam- sin vrne nov seznam, ki je enak podanemu seznamu, a brez- x-ov. Klic- brez([5, 4, 1, 2, 1, 1, 3], 1)vrne- [5, 4, 2, 3]. Te funkcije ni bilo v originalni nalogi Nim, a ti lahko pride prav pri naslednjem paketku nalog.
Rešitev
Spet naredimo natančno tako, kot pravi namig v nalogi: poiščemo največjo potenco (p), ki gre v n in potem razbijemo, kar ostane od n-ja. Končamo, ko je n nič in ni česa razbijati, tako da vrnemo prazen seznam.
def razbij(n):
    if n == 0:
        return []
    p = najvecja_potenca(n)
    return [p] + razbij(n - p)
Funkcijo brez bomo razpisali lepo počasi. Če je s prazen, vrnemo prazen seznam. Sicer pa imamo dve možnosti: če je prvi element ravno x, vrnemo ostanek brez x-a. Če je prvi elemement kaj drugega, pa vrnemo prvi element in ostanek brez x-a.
def brez(s, x):
    if not s:
        return []
    prvi, ostali = s[0], s[1:]
    if prvi == x:
        return brez(ostali, x)
    else:
        return [prvi] + brez(ostali, x)
Čisto dodatne naloge, v vaše veselje
- razlika(s, t)prejme dva seznama in vrne nov seznam, ki vsebuje elemente, ki se pojavijo v enem od podanih seznamov, ne pa v obeh. Klic- razlika([16, 4, 2], [4, 1])vrne- [16, 2, 1](v tem ali poljubnem drugem vrstnem redu), saj so to števila, ki se pojavijo le v prvem ali v drugem seznamu. Vsak element seznama- s(ali- t) se v tem seznamu pojavi le enkrat. Pri reševanju se ti morda (a ne nujno) splača uporabiti funkcijo, ki si jo sprogramiral prejle.
- razbij_seznam(s)prejme seznam števil in vrne seznam seznamov njihovih razbitij. Klic- razbij([22, 5, 13])vrne- [[16, 4, 2], [4, 1], [8, 4, 1]].
- zdruzi_sezname(s)prejme seznam seznamov (takšen, kot ga vrača funkcija- razbij_seznam) in vrne vse elemente, ki se pojavijo lihokrat.
Rešitev
razlika je ena lepših. Rekurzija bo tekla po s-ju: v vsakem klicu se bomo znebili prvega elementa s-ja.
Ustavila se bo, ko je t prazen. V tem primeru je razlika med s in t očitno s.
Če s ni prazen, moramo ločiti dva primera. Če je prvi element t-ja v s-ju (uporabili bomo rekurzivno funkcijo vsebuje), bomo vrnili razliko med s-jem brez tega elementa (ker ga je potrebno odstraniti od tam) in t-jem brez prvega elementa.
Če prvega elementa t-ja ni v s-ju, pa mora rezultat vsebovati ta element in še razliko med s in ostankom t-ja.
def razlika(s, t):
    if not t:
        return s
    prvi, ostali = t[0], t[1:]
    if vsebuje(s, prvi):
        return razlika(brez(s, prvi), ostali)
    else:
        return [prvi] + razlika(s, ostali)
Ostali dve čisto dodatni funkciji sta v primerjavi z njo trivialni.
def razbij_seznam(s):
    if not s:
        return []
    return [razbij(s[0])] + razbij_seznam(s[1:])
def zdruzi_sezname(ss):
    if not ss:
        return []
    return razlika(ss[0], zdruzi_sezname(ss[1:]))