Rešitev
Ogrevalna naloga: Pari hiš
Napišite funkcijo pari_his(stevilke), ki dobi seznam številk
in vrne množico vse parov hiš, ki imajo vsaj eno skupno števko.
Če gornji klic v resnici vrne
{(26, 23), (12, 26), (23, 26), (23, 12), (12, 23), (26, 12)}, se
ne vznemirjajte, saj je to v resnici eno in isto.
Namigi: najbrž boste potrebovali dvojno zanko. Ne pa trojne: razmislite, kako najpreprosteje ugotoviti, ali imata dve številki skupno števko. (Ne pozabite, da se učimo o množicah. In ne pozabite, da se s števkami najlažje dela, če spremenimo številko v niz.) Končno, ne spreglejte, da v množici ne sme biti parov ene in iste hiše, npr. (12, 12)
Če sta hisa1 in hisa2 dve številki, bosta
str(hisa1) in str(hisa2) niza s tema številkama.
Torej bosta set(str(hisa1)) in set(str(hisa2)) množici
števk, ki ju najdemo v teh nizih (oz. številkah). Zanima nas, ali imata
množici kak skupen element, torej preverimo, kakšen je njun presek,
set(str(hisa1)) & set(str(hisa2)). Tako kot nizi, seznami in
slovarji so tudi množice "resnične", kadar so neprazne in "neresnične", ko so
prazne.
Zdaj poglejmo celo funkcijo: kadar želimo sestaviti (ali pregledati ali narediti karkoli že s) pari elementov seznama, to storimo z dvojno zanko, v tem primeru
Za vsak par je potrebno preveriti, ali imata kakšen skupen element in ali gre za dve različni številki,
Zdaj pa še to, kar pride okoli: v začetku sestavimo prazno množico, vanjo zlagamo pare in to množico na koncu vrnemo.
Ne prezrite, da smo tule množice uporabili dvakrat. Enkrat za rezultat - ker je tako zahtevala naloga. Pare bi lahko zlagali tudi v seznam, če bi naloga hotela tako. Drugič smo množice uporabi interno, znotraj funkcije, za preverjanje skupnih števk. Naloga tega sicer ni zahtevala, bilo pa je praktično.
Za veselje nad tem, kar nas še čaka čez kak mesec, je tu rešitev v eni sami vrstici (ki smo jo samo zaradi preglednosti prelomili na pol).
Obvezna naloga: Legalne poti
Napišite funkciji legalna_pot(pot, pari) in
legalna_pot_bp(pot, pari).
Funkcija legalna_pot(pot, pari) prejme neko pot,
podano kot seznam, in množico parov, kakršne vrača funkcija
pari_his. Funkcija naj vrne True, če bo poštar
pripravljen prehoditi takšno pot, in False, če ne.
Druga funkcija, legalna_pot_bp(pot, pari), naj poleg tega
preveri, da se nobena hiša v poti ne ponovi.
Za vsak i moramo preveriti, da je par
(pot[i], pot[i + 1]) na seznamu dovoljenih parov. Pri tem i-ja ne
spustimo do range(len(pot)), kot bi bilo običajno, temveč le do
range(len(pot) - 1), saj zadnjega elementa nočemo primerjati z
njegovim (neobstoječim) naslednikom.
Vzorec je znan in dolgočasen: čim naletimo na kak par, ki ga ne bi smelo
biti, vrnemo False. Če takšnega para ni, torej za zanko,
vrnemo True.
Če se spomnimo funkcije zip, ne potrebujemo indeksov.
Z zipom, kot smo se učili, sestavimo dva seznama v seznam
parov (ali tri v seznam trojk in tako naprej). Tule sestavljamo seznama
pot in pot[1:], to je pot brez prvega
elementa. Tako dobimo točno tiste pare, ki jih želimo pregledati.
Čez kak mesec bomo znali še veliko elegantneje:
Legalna pot brez ponavljanj le še preveri, ali se kaka številka na poti ponovi. To najpreprosteje naredimo tako, da spremenimo seznam številk v množico številk in preverimo, ali ima ta še vedno toliko elementov kot seznam. Če jih nima, se je to zgodilo zato, ker se je (vsaj) ena od številk ponovila.
Dodatna naloga (čisto preprosta): Graf številk
Napišite funkcijo graf_his(pari), ki prejme seznam parov in
vrne graf, zapisan kot slovar, katerega ključi so točke (številke), vrednosti
pa množice točk, v katere je mogoče priti iz njih. Če jo pokličemo s pari
iz naloge z Bobra,
mora vrniti
Tu ni kaj filozofirati, posebej ne tistim, ki so vajeni uporabljati slovarje
s privzetimi vrednostmi: če je graf slovar, ki ga kani funkcija
vrniti, mora za vsak par (hisa1, hisa2) dodati v množico
graf[hisa1] element hisa2, torej
graf[hisa1].add(hisa2).
Dodatna naloga (za malo razmišljanja): Dosegljivost
Napišite funkcijo dosegljivi(stevilka, graf), ki prejme začetno
hišno številko in graf, kakršnega vrne prejšnja funkcija. Kot rezultat naj vrne
množico vseh hiš, ki so dosegljive iz določene hiše.
Takole se bomo lotili: imeli bomo množico preiskati, ki bo
vsebovala vse tiste točke grafa, za katere vemo, da je do njih mogoče priti,
njihovih sosedov pa še nismo pregledali. V množico dosegljivo pa
bomo zbirali vse dosegljive točke.
V začetku bo množica preiskati vsebovala začetno vozlišče,
množica dosegljivo pa bo prazna. Nato v vsakem koraku iz množice
preiskati vzamemo poljubno točko (dobimo jo s
preiskati.pop(), ki vrne neko točko iz množice in jo odstrani iz
nje). To točko dodamo med dosegljive, njene sosede pa dodamo v množico točk,
ki jih je potrebno še preiskati. Pri tem pa popazimo, da med točke, ki jim
moramo preiskati, ne dodamo, tistih, ki so bile že preiskane (torej dosegljive).
Vse skupaj ponavljamo, dokler imamo kaj preiskovati.
Dodatna naloga (za malo lomit zobe): Najdaljša pot
Rešite nalogo, ki so jo morali reševati otroci. ;)
Konkretno, napišite funkcijo najdaljsa_pot_od(zacetek, graf),
ki prejme začetno številko in graf, kot rezultat pa vrne seznam s številkami
na najdaljši poti, ki jo lahko opravi poštar, če začne na tej številki.
Nato napišite še funkcijo najdaljsa_pot(graf), ki vrne seznam
s številkami na najdaljši poti, pri čemer sama ugotovi, kje začeti.
Pri reševanju naloge vam bo zelo v pomoč, kar se bomo predvidoma učili prihodnji teden.
Prihodnji teden smo se učili rekurzijo.
Funkcijo se da obrniti na različne načine, večina "normalnih" načinov pa je
rekurzivnih. Za začetek bomo sestavili nekaj malenkost napačnega: rekurzivno
funkcijo
najdaljsa_0, ki kot argument dobi nek začetek poti, na primer
[83, 36, 61] in vrne najdaljšo pot, ki se začne s temi točkami,
recimo [83, 36, 61, 11, 19, 29, 52].
Prezrimo, da funkcija uporablja spremenljivko oz. argument
graf, ki ga nima. Ta problem se bo sam od sebe rešil kasneje.
Funkcija deluje takole: najprej reče, da je najdaljša pot (naj)
kar enaka začetku, ki ga je dobila kot argument. Nato poskusi vsa vozlišča, s
katerimi bi se pot lahko nadaljevala: zadnja točka na doslejšnji poti je
doslej[-1], njene sosede so graf[doslej[-1]], vendar
smemo pot nadaljevati le v točki, v kateri še nismo bili, zato od množice
sosednjih točk odštejemo množico točk, v katerih smo že bili,
Za vsako nadaljevanje se vprašamo, kakšna je najdaljša pot, ki se začne na ta na način,
Če je ta pot daljša od doslej najdaljše, si jo zapomni. Funkcija na koncu vrne najdaljšo pot.
Kar smo napisali, je že zelo podobno temu, kar bi morali napisati, le argumenti niso pravi: naloga zahteva funkcijo, ki sprejme začetno točko (ne seznama) in graf. Napačno funkcijo smo sprogramirali, ker moramo narediti rekurzivno funkcijo (če nočemo kakšne strašne, bistveno težje programerske telovadbe brez rekurzije). Iz tistega, kar je zahtevala naloga, se ne bi dalo narediti rekurzivne funkcije (vsaj ne da bi jaz vedel - vsaj na lep način ne). Zato smo pač sprogramirali, kar smo morali. Da dobimo, kar hoče naloga, bi lahko napisali funkcijo, kakršno hoče naloga, ki pa prepusti delo naši funkciji, s tem da jo pokliče s primernimi argumenti.
Da reč deluje, mora imeti najdaljsa_0 še argument
graf. To ni nujno potrebno: funkcijo najdaljsa_0 lahko
postavimo znotraj funkcije najdalja_pot_od:
Drugi del te naloge, funkcija najdaljsa_pot, je v primerjavi s
tem trivialen.