diff options
author | Martin Možina <martin.mozina@fri.uni-lj.si> | 2016-12-05 11:51:58 +0100 |
---|---|---|
committer | Martin Možina <martin.mozina@fri.uni-lj.si> | 2016-12-05 11:51:58 +0100 |
commit | 236001ec7563804f87a40c924681461bc8b2d764 (patch) | |
tree | 534a9ba942e6f6906d751439dff8864130a370ea /python/problems | |
parent | 413bb73adaee77e8c1ab1c5721b13d9b6f756a7a (diff) |
Added a new set of exercises (regular expressions).
Diffstat (limited to 'python/problems')
28 files changed, 2718 insertions, 0 deletions
diff --git a/python/problems/comprehensions/comprehensions_sl.html b/python/problems/comprehensions/comprehensions_sl.html new file mode 100644 index 0000000..4ebea98 --- /dev/null +++ b/python/problems/comprehensions/comprehensions_sl.html @@ -0,0 +1,572 @@ +<!DOCTYPE html>
+<html lang="sl">
+<head>
+<meta charset="utf-8" />
+<title></title>
+<link rel="stylesheet" type="text/css" href="/css/codeq.css" />
+<link rel="stylesheet" type="text/css" href="../../style.css" />
+</head>
+<body>
+
+<p>Matematiki si lahko privoščijo opisati množico dvakratnikov vseh naravnih
+ števil, katerih kvadrat je manjši od 120, takole:
+ \(S=\{\,2\cdot x\mid x \in \mathbb{N},\ x^2<120\,\}\). Mi tudi.</p>
+
+<h2>Seznami</h2>
+
+<p>Vendar pojdimo lepo počasi. Začnimo s tistim, kar najbolj poznamo: s seznami.
+ Doslej smo jih zapisali tako, da smo v oglatih oklepajih naštevali njihove
+ elemente,</p>
+
+<pre>k = [9, 16, 81, 25, 196]</pre>
+
+<p>Oglate oklepaje bomo uporabljali tudi poslej, le da znotraj njih ne bomo
+ našteli elementov, temveč napisali izraz, ki jih sestavi. Recimo, da bi
+ hotel seznam korenov števil iz gornjega seznama (po nekem čednem naključju
+ gornji seznam vsebuje ravno same kvadrate). Naredil bi tako:</p>
+
+<pre>>>> [sqrt(x) for x in k]
+[3.0, 4.0, 9.0, 5.0, 14.0]</pre>
+
+<p>Torej: napisal sem oglate oklepaje, kot vedno, kadar definiram seznam, a
+ namesto, da bi naštel elemente, sem povedal, kakšni naj bodo. Rekel sem,
+ naj bodo elementi mojega novega seznama <em>koren iz x</em>, pri čemer so
+ <code>x</code> elementi seznama <code>k</code>. Kaj pa, če bi hotel
+ namesto tega imeti kvadrate števil? Isti šmorn:</p>
+
+<pre>>>> [x**2 for x in k]
+[81, 256, 6561, 625, 38416]</pre>
+
+<p>Oblika tega, kar pišemo, je vedno
+ <code>[<em>izraz</em> for <em>spremenljivka</em> in <em>nekaj</em>]</code>,
+kjer je <em>spremenljivka</em> ime neke spremenljivke (npr. <code>x</code>,
+<em>izraz</em> nek izraz, ki (običajno, ne pa čisto nujno) vsebuje to
+spremenljivko (npr. <code>sqrt(x)</code> ali <code>2*x</code>, <em>nekaj</em>
+pa je nekaj, čez kar je možno spustiti zanko for, torej seznam, slovar,
+množica, datoteka ali kaj, česar še ne poznamo, vendar bomo spoznali še danes.
+</p>
+
+<p>Imam seznam imen, recimo,</p>
+<pre>l = ["Ana", "Berta", "Cilka", "Dani", "Ema"]</pre>
+
+<p>Kako bi izračunal poprečno dolžino imena? Imam funkcijo <code>sum</code>,
+ ki zna sešteti števila v seznamu. Potrebujem torej le še seznam dolžin
+ (namesto seznama imen). Tega pač preprosto dobim: izračunati želim dolžino
+ niza <code>ime</code> za vsako <code>ime</code> v seznamu
+ <code>imena</code>.</p>
+
+<pre>>>> [len(ime) for ime in imena]
+[3, 5, 5, 4, 3]</pre>
+
+<p>Zdaj pa tole le še seštejem in delim z dolžino seznama.</p>
+
+<pre>>>> sum([len(ime) for ime in imena]) / len(imena)
+4.0</pre>
+
+<p>Poprečna dolžina imena ni nič posebej zanimivega. Računajmo raje poprečno
+težo in, da nam ne bo dolgčas, jo bomo računali iz teh podatkov:</p>
+
+<pre>podatki = [
+ (74, "Anže", False),
+ (82, "Benjamin", False),
+ (58, "Cilka", True),
+ (66, "Dani", False),
+ (61, "Eva", True),
+ (84, "Franc", False),
+]</pre>
+
+<p>Takole nam je storiti: z zanko se zapeljemo čez podatke, izračunamo vsoto
+prvih elementov in jo delimo z dolžino seznama.</p>
+
+<p>Lepo prosim, ne tako:</p>
+
+<pre>vsota = 0
+for i in range(len(podatki)):
+ vsota += podatki[i][0]
+print(vsota / len(podatki))</pre>
+
+<p>Tole je že boljše:</p>
+
+<pre>vsota = 0
+for podatek in podatki:
+ vsota += podatek[0]
+print(vsota / len(podatki))</pre>
+
+<p>In še malo bolje:</p>
+
+<pre>vsota = 0
+for teza, ime, zenska in podatki:
+ vsota += teza
+print(vsota / len(podatki))</pre>
+
+<p>Zdaj pa naredimo tako, kot smo se naučili danes. Naredimo lahko
+
+<pre>vsota = sum([podatek[0] for podatek in podatki])</pre>
+
+<p>ali, po zadnji različici</p>
+
+<pre>vsota = sum([teza for teza, ime, zenska in podatki])</pre>
+
+<p>Pri izpeljevanju seznamov lahko dodamo še pogoj, s katerim izbiramo
+elemente, ki naj se dodajo.</p>
+
+<p>Kako bi sestavili seznam kvadratov vseh lihih
+števil do 100? Seznam kvadratov vseh števil do 100 je trivialen, napisati
+nam je treba le:</p>
+
+<pre>s = [x**2 for x in range(100)]</pre>
+
+<p>Če želimo pobrati samo liha števila, lahko v izpeljavo seznama dodamo še
+pogoj.</p>
+
+<pre>s = [x**2 for x in range(100) if x % 2 == 1]</pre>
+
+<p>Tole pravzaprav ni čisto potrebno, lahko bi rekli preprosto</p>
+
+<pre>s = [x**2 for x in range(1, 100, 2)]</pre>
+
+<p>Kaj pa, če bi hoteli seznam kvadratov vseh števil do 100, ki niso deljiva s
+ 7 in ne vsebujejo števke 7?</p>
+
+<pre>[x**2 for x in range(100) if (x % 7 != 0) and ("7" not in str(x))]</pre>
+
+<p>V pogoju smo zaradi preglednosti uporabili oklepaje, čeprav sicer niso
+potrebni). Prvi del poskrbi, da število ni deljivo s 7 (ostanek po deljenju s
+7 mora biti različni od 0). Drugi del poskrbi, da število ne vsebuje števke 7:
+število preprosto pretvorimo v niz in preverimo, da ne vsebuje sedemke.</p>
+
+<p>Kako bi izračunali vsoto tež moških v gornjem seznamu? Za zanko dodamo
+pogoj. Za začetek sestavimo le seznam imen vseh moških.</p>
+
+<pre>[ime for teza, ime, zenska in podatki if not zenska]</pre>
+
+<p>V definicijo našega seznama lepo na konec, za <code>for</code>, dodamo še
+<code>if</code> in pogoj, ki mu mora zadoščati element, da nas zanima.</p>
+
+<p>Z vsoto je podobno.</p>
+
+<pre>vsota = sum([teza for teza, ime, zenska in podatki if not zenska])</pre>
+
+<p>Zdaj veste, zakaj sem tako utrujal, da ne uporabljajte
+<code>for i in range(len(s))</code>: zato, ker z njim namesto lepega, kratkega
+preglednega <code>[ime for teza, ime, zenska in podatki if not zenska]</code>
+dobimo
+<code>[podatki[i][1] for i in range(len(podatki)) if not podatki[i][2]]</code>.
+Seveda lahko delate tudi na ta, drugi način. Svobodni ljudje ste in noben zakon
+ne prepoveduje mazohizma.</p>
+
+<p>Sestavimo seznam vseh deliteljev danega števila <code>n</code>. Vzemimo,
+ recimo <code>n = 60</code>. Seznam deliteljev je tedaj seznam vseh
+ <code>x</code>, pri čemer <code>x</code> prihaja z intervala
+ 1 ≤ x ≤ n, za katere velja, da je ostanek po deljenju
+ <code>n</code> z <code>x</code> enak 0 (torej <code>x</code> deli
+ <code>n</code>).</p>
+
+<pre>def delitelji(n):
+ return [x for x in range(1, n+1) if n % x == 0]</pre>
+
+<p>Zdaj lahko hitro ugotovimo, ali je dano število popolno: popolna
+števila so (se še spomnimo?) števila, ki so enaka vsoti svojih
+deliteljev (brez sebe samega).
+
+<pre>def popolno(n):
+ return n == sum(delitelji(n))</pre>
+</p>
+
+<p>Še bolj imenitna števila so praštevila, števila brez deliteljev. Se pravi
+ tista, pri katerih je seznam deliteljev med 2 in tem številom prazen.</p>
+
+<pre>def prastevilo(n):
+ return not delitelji(n)</pre>
+
+<p>Če funkcije <code>delitelji</code> še ne bi imeli - nič hudega. Ali je
+ število praštevilo, bi še vedno dognali v eni sami vrstici.</p>
+
+<pre>def prastevilo(n):
+ return not [x for x in range(2, n) if n % x == 0])</pre>
+
+<p>S to funkcijo hitro sestavimo seznam vseh praštevil med 2 in 100:</p>
+
+<pre>>>> [n for n in range(2, 101) if prastevilo(n)]
+[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]</pre>
+
+<p>Znamo pa, jasno, tudi brez funkcije; tisto, kar dela funkcija, pač kar
+prepišemo v pogoj.</p>
+
+<pre>>>> [n for n in range(2, 101) if not [x for x in range(2, n) if n % x == 0])]
+[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]</pre>
+
+<p>Takšnih reči sicer raje ne pišemo, saj hitro postanejo nepregledne.</p>
+
+<p>Kdor je gledal stare naloge, je opazil nalogo z vislicami. V eni od nalog
+ je bilo potrebno napisati funkcijo <code>pokazi_crke(beseda, crka)</code>,
+ ki kot argument dobi besedo in množico črk, vrniti pa mora besedo, v kateri
+ so "vidne" vse črke, ki so v množici, tiste, ki jih ni, pa je potrebno
+ zamenjati s piko. Tako mora, recimo,
+ <code>pokazi_crke("BESEDA", {"E", "D"})</code> vrniti <code>.E.ED.</code>.
+</p>
+
+<p>Nalogo rešimo lepo po korakih. Vzemimo neko množico črk in neko besedo.</p>
+
+<pre>>>> crke = {"E", "D"}
+>>> beseda = "BESEDA"</pre>
+
+<p>Zdaj razsekajmo besedo na črke.</p>
+
+<pre>>>> [b for b in beseda]
+['B', 'E', 'S', 'E', 'D', 'A']</pre>
+
+<p>Zdaj pa naj seznam vsebuje <code>b</code> le, če je <code>b</code> v množici
+ <code>crke</code>, sicer pa piko.</p>
+
+<pre>>>> [b if b in crke else "." for b in beseda]
+['.', 'E', '.', 'E', 'D', '.']</pre>
+
+<p>Da združimo črke v niz, potrebujemo le še večno zeleni <code>join</code>.</p>
+
+<pre>>>> "".join([b if b in crke else "." for b in beseda])
+'.E.ED.'</pre>
+
+<p>Celotna rešitev naloge je torej</p>
+
+<pre>def pokazi_crke(beseda, crke):
+ print "".join((c if c in crke else ".") for c in beseda)
+</pre>
+
+<p>V splošnem: vsak kos kode, ki izgleda takole</p>
+
+<pre>r = []
+for e in s:
+ if pogoj(e):
+ r.append(funkcija(e))</pre>
+
+<p>lahko prepišemo v</p>
+
+<pre>r = [funkcija(e) for e in s if pogoj(e)]</pre>
+
+<p>Kdor želi še kakšen primer, si bo ogledal
+ <a href="/mod/page/view.php?id=11856">rešitev
+ kronogramov</a> in
+ <a href="/mod/page/view.php?id=11814">napadalnih kraljic</a>.
+</p>
+
+<h2>Množice</h2>
+
+<p>Množice sestavljamo natančno tako kot sezname, le da namesto oglatih
+ oklepajev uporabimo zavite. Takole dobimo vse delitelje 60</p>
+
+<pre>>>> {i for i in range(1, 61) if 60 % i == 0}
+{1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 60, 30}</pre>
+
+
+<h2>Slovarji</h2>
+
+<p>Ista reč. Če hočemo narediti slovar, ki bo kot ključe vseboval števila do
+ 10, kot vrednosti pa njihove kvadrate, napišemo</p>
+
+<pre>{i: i**2 for i in range(11)}</pre>
+
+<h2>Generatorji</h2>
+
+<p>Tule bi pričakovali nekaj drugega: terke. Lahko tako kot sezname, množice in
+ slovarje naredimo tudi terke in nize? Ne. Na takšen način določeni nizi
+ očitno ne bi imeli nobenega smisla, pa tudi terke ne bi bile preveč
+ uporabne. Pač pa nas čaka - in bo pogosto v okroglih oklepajih - še nekaj
+ bolj nenavadnega in imenitnejšega: generatorji.</p>
+
+<pre>>>> g = (x**2 for x in range(10))</pre>
+
+<p>Tale <code>g</code>, kot se hitro prepričamo, ni terka, temveč nekaj
+ drugega.</p>
+
+<pre>>>> g
+<generator object <genexpr> at 0x667bc0></pre>
+
+<p>Kaj je generator, čemu služi in kako ga uporabljamo? Na prvi dve vprašanji
+ lahko odgovorimo naenkrat: generator je nekaj, kar generira objekte.
+ Vsakič, ko bomo od njega zahtevali nov objekt, bo vrnil novo število -
+ najprej 0, potem 1, potem 4, potem 9 ... pač kvadrate naravnih števil do
+ 10. Na tretje vprašanje, kako ga uporabljamo, nam skoraj ni potrebno
+ odgovarjati, saj generatorje (in njim podobne reči) že dolgo uporabljamo.
+ Generatorje najpreprosteje uporabimo z zanko for. Še več, zanka for deluje
+ na tistih rečeh - in samo tistih rečeh - ki se znajo obnašati kot
+ generatorji (torej: v resnici so generatorji ali pa podpirajo osnovne
+ funkcije generatorjev).</p>
+
+<pre>>>> for i in g:
+... print(i, end=" ")
+...
+0 1 4 9 16 25 36 49 64 81</pre>
+
+<p>Če poskusimo ponoviti, ne gre: generator je že "iztrošen". Kar je imel
+ zgenerirati, je zgeneriral.</p>
+
+<pre>>>> for i in g:
+... print(i, end=" ")
+...
+>>></pre>
+
+<p>Generator je torej videti kot izpeljani seznam, le da namesto oglatih
+ oklepajev uporabimo okrogle. Kadar ga pošljemo kot edini argument funkciji,
+ smemo oklepaje celo izpustiti. Napišimo funkcijo, ki kot argument dobi
+ seznam števil in vrne prvo število, ki je večje od 50.</p>
+
+<pre>def nad50(s):
+ for e in s:
+ if e > 50:
+ return e</pre>
+
+<p>Preskusimo jo, trikrat:</p>
+
+<pre>>>> nad50([5, 1, 40, 1, 82, 12, 6])
+82
+>>> nad50([x**2 for x in range(10)])
+64
+>>> nad50(x**2 for x in range(10))
+64</pre>
+
+<p>V prvem primeru je dobila najobičajnejši seznam. Tako smo si predstavljali,
+ ko smo jo pisali. V drugem primeru smo ji dali seznam kvadratov števil do
+ 10 in vrnila je prvi kvadrat, ki je večji od 50. V tretjem klicu pa ji
+ sploh nismo dali seznama, temveč generator, ki je dajal kvadrate -
+ natančno isto reč kot gornji <code>g</code>. Funkcija je čez generator
+ pognala zanko <code>for</code> natančno tako, kot jo sicer poganja čez
+ sezname.</p>
+
+<p>V čem je prednost generatorjev pred seznami? V tem, da ne sestavijo seznama.
+ Če želimo izračunati vsoto kvadratov prvih stotih števil in za to uporabimo
+ generator, na ta način ne sestavimo seznama teh števil (kot bi ga, če bi
+ rekli <code>sum([x**2 for x in range(100)])</code>, temveč števila, namreč
+ kvadrate, generiramo sproti (tako, da pokličemo
+ <code>sum([x**2 for x in range(100)])</code>. Hm, pa to v resnici deluje?
+ No, seveda. Funkcija <code>sum</code> bi lahko bila napisana takole</p>
+
+ <pre>def sum(s):
+ vsota = 0
+ for e in s:
+ vsota += e
+ return vsota</pre>
+
+ <p>Zanka for lahko gre prek generatorja (in v resnici vedno gre prek
+ generatorja), torej ona reč deluje.</p>
+
+ <p>Že, že, poreče pozornejši študent: kaj pa <code>range(100)</code>? Mar ta
+ ne sestavi seznama stotih števil? Smo res kaj dosti pridobili - namesto
+ seznama kvadratov števil do 100 imamo pač števila do 100 - je to res tak
+ napredek? Tu lahko študenta pohvalimo za budnost, vendar se moti.</p>
+
+ <pre>>>> range(100)
+ range(0, 100)</pre>
+
+ <p>V resnici <code>range</code> ne sestavi seznama, temveč generator. Res se
+ nam ni predstavil kot kak "generator object" temveč kot "range(0, 100)",
+ vendar v resnici ni ničesar sestavil. Nobenega seznama s stotimi števili
+ ni. Kako vem? Preprosto: </p>
+
+ <pre>>>> range(1000000000000)
+ range(0, 1000000000000)</pre>
+
+ <p>Če bi <code>range</code> sestavljal sezname, bi bil to seznam z bilijon
+ elementi. Če bi vsak od njih zavzel samo en bajt (pa jih v resnici najbrž
+ vsaj 20, verjetneje pa 32), bi shranjevanje takšnega seznama zahtevalo
+ pomnilnik velik en terabajt.</p>
+
+<p>Tako smo torej generatorje uporabljali že, odkar vemo za <code>range</code>.
+ (Morda se kdo spomni, da sem vam takrat, na onem davnem predavanju, ko sem
+ povedal za funkcijo <code>range</code>, le-to pokazal na Pythonu 2.7? Tako
+ je bilo lažje, saj je takrat še vračal sezname. To je ena od sprememb od
+ različice 3.0 naprej: po novem vrača generatorji.)</p>
+
+<p>Podobno je s slovarji: metode <code>__keys__</code>, <code>__values__</code>
+ in <code>__items__</code> vračajo generatorje, ne seznamov.</p>
+
+<h2>Funkcije, ki generirajo</h2>
+
+<p>Še nekoliko naprednejša snov: Bi znali napisati generator Fibonaccijevih
+ števil, tako kot smo napisali generator kvadratov? Da, vendar bo
+ preprostejša nekoliko drugačna pot. Napisali bomo funkcijo, ki ne vrača le
+ enega rezultata temveč "generira" rezultate. Torej, nekaj takšnega:</p>
+
+<pre>def fibonacci(n):
+ a = b = 1
+ for i in range(n):
+ return a # Tole ne deluje!
+ a, b = b, a+b</pre>
+
+<p>Tale funkcija ne deluje, napisali smo jo le, da ilustriramo idejo: radi bi
+ naredili zanko. Ko funkcijo prvič pokličemo, bi <code>return</code> vrnil
+ prvo Fibonaccijevo število. Ko jo pokličemo naslednjič, se funkcija ne bi
+ izvajala od začetka, temveč od tam, kjer smo nazadnje vrnili rezultat, se
+ pravi z vrstico, ki sledi <code>return</code>u. No, v resnici naredimo
+ natanko tako, le namesto <code>return</code>a moramo uporabiti
+ <code>yield</code>:</p>
+
+<pre>def fibonacci(n):
+ a = b = 1
+ for i in range(n):
+ yield a
+ a, b = b, a+b</pre>
+
+<p>Preskusimo.</p>
+
+<pre>>>> for i in fibonacci(5):
+... print(i)
+1
+1
+2
+3
+5</pre>
+
+<p>Napišemo lahko celo funkcijo, ki vrne (no, generira) <b>vsa</b>
+ Fibonaccijeva števila.</p>
+
+<pre>def fibonacci():
+ a = b = 1
+ while True:
+ yield a
+ a, b = b, a+b</pre>
+
+<p>Neskončno zanko, <code>while True</code>, smo že videli, vendar je bil v
+ njej vedno <code>break</code>, ki jo je nekoč prekinil. Kdo pa prekine to
+ zanko? Če nismo previdni, nihče. Če rečemo</p>
+
+<pre>>>> for i in fibonacci():
+... print(i)</pre>
+
+<p>bo program izpisoval v nedogled. Pač pa lahko poiščemo, recimo, prvo
+ Fibonaccijevo število, ki je večje od 50.</p>
+
+<pre>>>> for i in fibonacci():
+... if i > 50:
+... print(i)
+... break
+55</pre>
+
+<p>Ali pa, ah, saj imamo za to že funkcijo, <code>nad50</code>. Naj kar ta
+pove, katero je prvo Fibonaccijevo število večje od 50!</p>
+
+<pre>>>> nad50(fibonacci())
+55</pre>
+
+<p>V zvezi z vsemi temi rečmi bi bilo mogoče povedati še zelo veliko. Tole je
+ ena najbolj kul tem v Pythonu. Pravzaprav treba priznati, da to niti ni
+ tema iz Pythona. V ozadju tega, kar počnemo tule, je poseben slog
+ programiranja,
+ <a href="https://en.wikipedia.org/wiki/Functional_programming">funkcijsko
+ programiranje</a>. Python ga omogoča in med "normalnimi" jeziki je za
+ takšen slog pravzaprav eden boljših. Obstajajo pa jeziki, ki so posebej
+ narejeni za takšno programiranje. Če je bilo komu tole, kar smo počeli
+ doslej, všeč naj si nujno ogleda kak
+ <a href="https://en.wikipedia.org/wiki/Standard_ML">SML</a> ali
+ <a href="https://en.wikipedia.org/wiki/Haskell_(programming_language)">
+ Haskell</a>, morda pa ga bo zabaval tudi
+ <a href="https://en.wikipedia.org/wiki/Racket_(programming_language)">
+ Racket</a> (dialekt Lispa).</p>
+
+<p>V Pythonu pa si bo ta, ki so mu bile te reči všeč, dobro ogledal module
+<a href="https://docs.python.org/3/library/functools.html">functools</a>,
+<a href="https://docs.python.org/3/library/itertools.html">iterools</a> in
+<a hreF="https://docs.python.org/3/library/operator.html">operator</a>.</p>
+
+<h3>Generator deliteljev</h3>
+
+<p>Še en zanimiv primer je generator, ki vrne vse delitelje podanega števila.
+</p>
+
+<pre>def delitelji(n):
+ for i in range(1, n + 1):
+ if n % i == 0:
+ yield i</pre>
+
+<p>Opazimo lahko, da z enim deliteljem dobimo dva: če je <code>i</code> delitelj
+<code>n</code>-ja, je tudi <code>n // i</code> delitelj <code>n</code>-ja.
+Če je tako, zadošča, da gremo do korena iz <code>n</code> in vrnemo po dva
+delitelja.</p>
+
+<pre>from math import sqrt
+
+def delitelji(n):
+ for i in range(1, int(sqrt(n) + 1)):
+ if n % i == 0:
+ yield i
+ yield n // i</pre>
+
+<p>Koren iz <code>n</code> moramo spremeniti v celo število, ker <code>range</code>
+ne mara necelih.</p>
+
+<p>Pazite, tole sta dva <code>yield</code>a. Funkcija se izvaja tako, ad vrne
+najprej eno število, in ko zahtevamo naslednje, se izvede naslednji
+<code>yield</code>.</p>
+
+<p>Funkcija je zdaj bistveno hitrejša (pri velikih številih bi se to utegnilo
+kar poznati - namesto, da gre do milijona, bo šla le do 1000. Vendar žal ne
+dela pravilno. Če je <code>n</code>, recimo, <code>25</code>, bo funkcija
+dvakrat vrnila 5. A tega se znamo hitro znebiti.</p>
+
+<pre>from math import sqrt
+
+def delitelji(n):
+ for i in range(1, int(sqrt(n) + 1)):
+ if n % i == 0:
+ yield i
+ if i ** 2 != n:
+ yield n // i</pre>
+
+<p>Zdaj nas morda moti le še to, da števila ne prihajajo v pravem vrstnem redu.
+Kot delitelje 42 bi namesto 1, 2, 3, 6, 7, 14, 21, 42 dobili 1, 42, 2, 21, 3,
+14, 6, 7. To lahko popravimo tako, da <code>n // i</code> ne vračamo sproti,
+temveč jih le shranjujemo in jih vračamo kasneje.</p>
+
+<pre>from math import sqrt
+
+def delitelji(n):
+ for i in range(1, int(sqrt(n) + 1)):
+ if n % i == 0:
+ yield i
+ if i ** 2 != n:
+ ostali.append(n // i)
+ for e in ostali:
+ yield e</pre>
+
+<p>Nismo še čisto zmagali. Zdaj imamo 1, 2, 3, 6, 41, 21, 14, 7 - prva polovica
+je iz prvega <code>yield</code>a, druga (od 41 naprej) iz drugega. Te, druge,
+vrača v enakem vrstnem redu, v katerem jih je vstavljal v seznam, saj
+<code>append</code> pač vstavlja na konec.</p>
+
+<p>Pa vstavljajmo raje na začetek! Uporabimo <code>insert</code>.</p>
+
+<pre>from math import sqrt
+
+def delitelji(n):
+ for i in range(1, int(sqrt(n) + 1)):
+ if n % i == 0:
+ yield i
+ if i ** 2 != n:
+ ostali.insert(0, n // i)
+ for e in ostali:
+ yield e</pre>
+
+<p>Žal se je <code>insert</code>u modro izogibati. Za to, da vstavi element
+na prvo mesto, mora enega za drugim premakniti vse ostale. V teoriji (ki se jo
+boste učili drugo leto) je ta program enako počasen kot bi bil, če bi prvo
+zanko spustili prek <code>range(1, n + 1)</code>. S tem, ko smo zamenjali
+<code>append</code> z <code>insert</code>, smo, vsaj v teoriji, zapravili ves
+prihranek.</p>
+
+<p>To je preprosto urediti. Vstavljali bomo na konec, z <code>append</code>.
+Pač pa bomo seznam nato prehodili v obratnem vrstnem redu. To se da narediti
+z indeksiranjem (<code>for i in range(-1, -n - n, -1): yield ostali[i]</code>,
+vendar si bomo raje pomagali s priročno funkcijo <code>reversed</code>, ki
+obrne seznam.</p>
+
+<pre>def delitelji(n):
+ ostali = []
+ for i in range(1, int(sqrt(n)) + 1):
+ if n % i == 0:
+ yield i
+ if i ** 2 != n:
+ ostali.append(n // i)
+ for e in reversed(ostali):
+ yield e</pre>
+
+</html>
diff --git a/python/problems/dictionaries/dict_sl.html b/python/problems/dictionaries/dict_sl.html new file mode 100644 index 0000000..8b7966d --- /dev/null +++ b/python/problems/dictionaries/dict_sl.html @@ -0,0 +1,598 @@ +<!DOCTYPE html>
+<html lang="sl">
+<head>
+<meta charset="utf-8" />
+<title></title>
+<link rel="stylesheet" type="text/css" href="/css/codeq.css" />
+<link rel="stylesheet" type="text/css" href="../../style.css" />
+</head>
+<body>
+
+<h2>Slovar</h2>
+
+<p>Poglejmo še enkrat sezname. Seznam je nekakšna zbirka nekih poljubnih
+ reči, pri čemer je vsaka reč v svojem "predalčku", predalčki pa so
+ oštevilčeni. Prvi ima številko 0, drugi 1, tretji 2 in tako naprej. Za
+ vsak predalček lahko pogledamo, kaj je v njem, spremenimo njegovo vsebino,
+ predalčke lahko odvzemamo in dodajamo.
+
+<pre>>>> s = [6, 9, 4, 1]
+>>> s[1]
+9
+>>> s.append(7)
+>>> s
+[6, 9, 4, 1, 7]
+>>> del s[3]
+>>> s
+[6, 9, 4, 7]
+>>> s[-2:]
+[4, 7]</pre>
+
+<p>Slovar (angl. <em>dictionary</em>, podatkovni tip pa se imenuje
+ <code>dict</code>) je podoben seznamu, le da se na predalčke sklicujemo z
+ njihovimi <em>ključi</em> (angl. <em>key</em>, ki so lahko števila, lahko
+ pa tudi nizi, logične vrednosti, terke... Temu, kar je shranjeno v predalčku
+ bomo rekli <em>vrednost</em> (<em>value</em>). </p>
+
+<p>Seznam in slovar se že od daleč (če niste kratkovidni) razlikujeta po
+ oglatosti. Seznam smo opisali tako, da smo v <em>oglatih oklepajih</em>
+ našteli njihove <em>elemente</em>. Slovar opišemo tako, da v <em>zavitih
+ oklepajih</em> naštejemo pare <em>ključ: vrednost</em>.
+
+<p>Stalno omenjani Benjamin si lahko takole sestavi slovar s telefonskimi
+ številkami svojih oboževalk:
+<pre>stevilke = {"Ana": "041 310239", "Berta": "040 318319", "Cilka": "041 103194", "Dani": "040 193831",
+ "Eva": "051 123123", "Fanči": "040 135367", "Helga": "+49 175 4728 475"}</pre>
+
+<p>Do vrednosti, shranjenih v slovarju, pride podobno kot do vrednosti v
+ seznamu: z indeksiranjem, le da v oglate oklepaje ne napiše zaporedne
+ številke, temveč ključ.</p>
+
+<pre>>>> stevilke["Ana"]
+'041 310239'
+>>> stevilke["Dani"]
+'040 193831'</pre>
+
+<p>Slovarji ne poznajo vrstnega reda. V slovarju ni prvega, drugega, tretjega
+ elementa. Ne le zato, ker oznake niso številke. Ne, slovar si v resnici ne
+ zapomni vrstnega reda, v katerem smo našteli elemente, niti ne vrstnega
+ reda, v katerem jih dodajamo.</p>
+
+<pre>>>> stevilke
+{'Dani': '040 193831', 'Fanči': '040 135367', 'Helga': '+49 175 4728 475',
+'Eva': '051 123123', 'Cilka': '041 103194', 'Berta': '040 318319',
+'Ana': '041 310239'}</pre>
+
+<p>(Čemu?! Čemu je to potrebno? Čemu zmeša vrstni red?! Mar ne more zlagati
+ teh reči lepo po vrsti? Izvedeli boste drugo leto. V resnici ni zmešan,
+ temveč prav premeteno premetan. V tem, drugače preurejenem seznamu lahko išče
+ veliko hitreje.)</p>
+
+<p>Ker ni vrstnega reda, tudi ni rezin.</p>
+
+<pre>>>> stevilke["Ana":"Cilka"]
+Traceback (most recent call last):
+ File "<interactive input>", line 1, in <module>
+TypeError: unhashable type</pre>
+
+<p>Tole sporočilo o napaki je sicer zelo čudno, vendar povsem smiselno. Boste
+razumeli, ko boste veliki. ;)</p>
+
+<p>O "mehaniki" slovarjev morate vedeti le tole: slovarji so hitri, saj
+ elementov v resnici ne iščejo. Na nek za zdaj skrivnosten način "vedo", kje
+ je vrednost, ki pripada danemu ključu. Ne da bi iskali, vedo, kam morajo
+ pogledati. Tudi dodajanje novih elementov v slovar je zelo hitro, enako tudi
+ brisanje. Cena, ki jo plačamo za to, je dvojna. Ključi so lahko le
+ podatkovni tipi, ki so nespremenljivi. Nespremenljivi podatkovni tipi so,
+ vemo, nizi, števila, logične vrednosti in terke. Zgoraj smo kot ključ
+ uporabili niz. To je OK. Če bi poskušali kot ključ uporabiti seznam, ne bi
+ šlo. (V resnici je stvar malenkost bolj zapletena, ključ je lahko vsak
+ podatkovni tip, ki je "razpršljiv", vendar to ni za bruce). Omejitve
+ veljajo le za ključe. Vrednost je lahko karkoli.</p>
+
+<p>Drugi obrok cene, ki jo plačamo za učinkovitost slovarjev, je poraba
+ pomnilnika: slovar porabi nekako dvakrat več pomnilnika kot seznam. Koliko,
+ točno, več, je odvisno od velikost slovarja in tega, kaj shranjujemo.
+ Pri tem predmetu se s pomnilnikom ne obremenjujte.</p>
+
+<p>Aha, še tretji obrok cene, iz drobnega tiska: vsak ključ se lahko pojavi
+ največ enkrat. Če poskušamo prirediti neko vrednost ključu, ki že obstaja, le
+ povozimo staro vrednost. Ampak to je tako ali tako pričakovati. Tudi v
+ seznamu nimamo nikoli dveh različnih elementov na istem mestu.</p>
+
+<h4>Kaj lahko počnemo s slovarji?</h4>
+
+<p>Lahko jim dodajamo nove elemente: preprosto priredimo jim nov element.</p>
+
+<pre>>>> stevilke["Iva"] = "040 222333"
+>>> stevilke
+{'Dani': '040 193831', 'Fanči': '040 135367', 'Iva': '040 222333',
+'Helga': '+49 175 4728 475', 'Eva': '051 123123', 'Cilka': '041 103194',
+'Berta': '040 318319', 'Ana': '041 310239'}</pre>
+
+<p><code>append</code> ne obstaja, saj nima smisla: ker ni vrstnega reda, ne
+moremo dodajati na konec. Prav tako (ali pa še bolj) ne obstaja
+<code>insert</code>, saj prav tako (ali pa še bolj) nima smisla.
+
+<p>Vprašamo se lahko, ali v seznamu obstaja določen ključ.</p>
+
+<pre>>>> "Cilka" in stevilke
+True
+>>> "Jana" in stevilke
+False</pre>
+
+<p>Če se Cilka skrega z Benjaminom, jo lahko le-ta pobriše (mislim, pobriše
+ njeno številko).</p>
+
+<pre>>>> del stevilke["Cilka"]
+>>> stevilke
+{'Dani': '040 193831', 'Fanči': '040 135367', 'Iva': '040 222333',
+'Helga': '+49 175 4728 475', 'Eva': '051 123123',
+'Berta': '040 318319', 'Ana': '041 310239'}
+>>> "Cilka" in stevilke
+False</pre>
+
+<p>Če gremo prek slovarjev z zanko <code>for</code>, dobimo vrednosti
+ ključev.</p>
+
+<pre>>>> for ime in stevilke:
+... print(ime)
+...
+Dani
+Fanči
+Iva
+Helga
+Eva
+Berta
+Ana</pre></p>
+
+Seveda lahko ob vsakem imenu izpišemo tudi številko.
+
+<pre>>>> for ime in stevilke:
+... print(ime + ":", stevilke[ime])
+...
+Dani: 040 193831
+Fanči: 040 135367
+Iva: 040 222333
+Helga: +49 175 4728 475
+Eva: 051 123123
+Cilka: 041 103194
+Berta: 040 318319
+Ana: 041 310239</pre>
+
+<p>Vendar to ni najbolj praktično. Pomagamo si lahko s tremi metodami slovarja,
+ ki vrnejo vse ključe, vse vrednosti in vse pare ključ-vrednost. Imenujejo
+ se (ne preveč presenetljivo) <code>keys()</code>, <code>values()</code> in
+ <code>items()</code>. Delamo se lahko, da vračajo sezname ključev,
+ vrednosti oziroma parov ... čeprav v resnici vračajo le nekaj, prek česar
+ lahko gremo z zanko <code>for</code>.
+
+<p>Najprej poglejmo <code>items()</code>.</p>
+
+<pre>>>> for t in stevilke.items():
+... ime, stevilka = t
+... print(ime + ":", stevilka)</pre>
+
+<p>Tole zgoraj je bilo seveda grdo. Lepo se reče takole:</p>
+
+<pre>>>> for ime, stevilka in stevilke.items():
+... print(ime + ":", stevilke)</pre>
+
+<p>Tako kot sem ob zanki for težil, da ne uporabljajte
+<code>for i in range(len(s))</code> temveč <code>for e in s</code> in da
+že v glavi zanke razpakirajte terko, kadar je to potrebno, bom težil tudi
+tule: uporabljajte <code>items</code> in vaši programi bodo preglednejši in
+s tem pravilnejši.</p>
+
+<p>Metoda <code>values</code> vrne vse vrednosti, ki so v slovarju. V našem
+ primeru torej telefonske številke. Metodo <code>values</code> človek včasih
+potrebuje, prav pogosto pa ne.</p>
+
+<p>V nasprotju s tem metodo <code>keys</code> potrebujejo samo študenti. Ne vem
+točno, zakaj sploh obstaja. No, vem, zato da študenti lahko pišejo
+<code>for ime in stevilke.keys()</code> namesto
+<code>for ime in stevilke</code>. Druge uporabne vrednosti pa ne vidim. :)</p>
+
+<p>Skratka: ne uporabljajte metode <code>keys</code>.</p>
+
+<p>Slovar ima še dve metodi, ki smo ju videli tudi pri seznamu: metodo, ki
+ sprazni slovar in drugo, ki naredi nov, enak slovar.</p>
+
+<pre>>>> stevilke2 = stevilke.copy()
+>>> stevilke2
+{'Dani': '040 193831', 'Fanči': '040 135367', 'Iva': '040 222333',
+'Helga': '+49 175 4728 475', 'Eva': '051 123123',
+'Berta': '040 318319', 'Ana': '041 310239'}
+>>> stevilke.clear()
+>>> stevilke
+{}
+>>> stevilke2
+{'Dani': '040 193831', 'Fanči': '040 135367', 'Iva': '040 222333',
+'Helga': '+49 175 4728 475', 'Eva': '051 123123',
+'Berta': '040 318319', 'Ana': '041 310239'}</pre>
+
+<p>Omenimo le še eno od slovarjevih metod, <code>get</code>. Ta dela podobno
+ kot indeksiranje, <code>stevilke.get("Ana")</code> naredi isto kot
+ <code>stevilke["Ana"]</code>. Metodo <code>get</code> uporabimo, kadar ni
+ nujno, da je ključ v seznamu in želimo v tem primeru dobiti neko privzeto
+ vrednost. Privzeto vrednost podamo kot drugi argument.</p>
+
+<pre>>>> stevilke.get("Ema", "ni številke")
+'040 584928'
+>>> stevilke.get("Greta", "ni številke")
+'nimam stevilke'</pre>
+
+<p>Še enkrat: ne pišite <code>stevilke.get("Ema")</code>. To je čudno.
+Piše se <code>stevilke["Ema"]</code>. Metodo <code>get</code> uporabite le
+takrat, kadar niste prepričani, da slovar vsebuje ta ključ.</p>
+
+<h4>Primer: kronogrami</h4>
+
+<p>Neka naloga je šla takole.</p>
+
+<p>Veliko latinskih napisov, ki obeležujejo kak pomemben dogodek, je napisanih
+ v obliki <a href="http://en.wikipedia.org/wiki/Chronogram">kronograma</a>:
+ če seštejemo vrednosti črk, ki predstavljajo tudi rimske številke (I=1, V=5,
+ X=10, L=50, C=100, D=500, M=1000), dajo letnico dogodka.</p>
+
+<p>Tako, recimo, napis na cerkvi sv. Jakoba v Opatiji, CVIVS IN HOC RENOVATA
+ LOCO PIA FVLGET IMAGO SIS CVSTOS POPVLI SANCTE IACOBE TVI, da vsoto 1793,
+ ko je bila cerkev prenovljena (o čemer govori napis).</p>
+
+<p>Pri tem obravnavamo vsak znak posebej: v besedil EXCELSIS bi prebrali
+ X + C + L + I = 10 + 100 + 50 + 1 = 161 in ne
+ XC + L + I = 90 + 50 + 1 = 141.</p>
+
+<p>Napiši program, ki izračuna letnico za podani niz.</p>
+
+<p>Očitna rešitev je:</p>
+
+<pre>def kronogram(s):
+ v = 0
+ for c in s:
+ if c=="I":
+ v += 1
+ elif c=="V":
+ v += 5
+ elif c=="X":
+ v += 10
+ elif c=="L":
+ v += 50
+ elif c=="C":
+ v += 100
+ elif c=="D":
+ v += 500
+ elif c=="M":
+ v += 1000
+ return v</pre>
+
+<p>Pri njej vsi, ki poznajo stavek <code>switch</code> oz. <code>case</code>
+postokajo, kako je možno, da Python tega stavka nima. (Pustimo ob strani,
+ali je tole res toliko daljše od <code>switch</code>, sploh če program nespodobno
+nabijemo skupaj:</p>
+<pre>def kronogram(s):
+ v = 0
+ for c in s:
+ if c=="I": v += 1
+ elif c=="V": v += 5
+ elif c=="X": v += 10
+ elif c=="L": v += 50
+ elif c=="C": v += 100
+ elif c=="D": v += 500
+ elif c=="M": v += 1000
+ return v</pre>
+<p>). Ne, nima ga, ker ga skoraj nikoli ne potrebujemo. Tisto, kar delamo z
+njim, pogosto rešimo (tudi) s slovarji.</p>
+
+<p>V slovar si bomo zapisali, katero številko pomeni katera črka.</p>
+
+<pre>stevke = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}</pre>
+
+<p>Funkcija zdaj deluje tako, da gre prek niza in za vsako črko preveri, ali je v
+slovarju. Če je ni, ne pomeni številke; če je, prištejemo toliko, kolikor je
+vredna.</p>
+
+<pre>def kronogram(s):
+ v = 0
+ for c in s:
+ if c in stevke:
+ v += stevke[c]
+ return v</pre></p>
+
+<p>Še hitreje gre z <code>get</code>; če črke ni, je njena privzeta vrednost 0.
+ </p>
+<pre>def kronogram(s):
+ v = 0
+ for c in s:
+ v += stevke.get(c, 0)
+ return v</pre>
+
+<p>Ob tem si ne moremo kaj, da ne bi poškilili za en mesec naprej, ko se bomo
+ učili funkcijskega programiranja in znali biti še krajši in jedrnatejši,
+ spet predvsem po zaslugi slovarjev.</p>
+
+<pre>def kronogram(s):
+ return sum(stevke.get(c, 0) for c in s)</pre>
+
+
+<h2>Slovarji s privzetimi vrednostmi</h2>
+
+<p>Slovarji so uporabne reči. V veliko primerih pa uporabimo neko različico
+slovarja, ki ima še eno dodatno sposobnost.</p>
+
+<p>Denimo, da smo dobili v preskušanje igralno kocko, za katero nas zanima, ali
+ goljufa. Stokrat smo jo vrgli in zabeležili rezultate.</p>
+
+<pre>meti = [4, 4, 4, 3, 2, 3, 5, 3, 3, 4, 6, 6, 6, 1, 3,
+ 6, 6, 4, 1, 4, 6, 1, 4, 4, 4, 6, 4, 6, 5, 5, 6, 6, 2, 4, 4, 6,
+ 3, 2, 6, 1, 3, 6, 3, 2, 6, 6, 4, 6, 4, 2, 4, 4, 1, 1, 6, 2, 6,
+ 6, 4, 3, 4, 2, 6, 5, 6, 3, 2, 5, 1, 5, 3, 6, 4, 6, 2, 2, 4, 1,
+ 4, 4, 3, 1, 4, 2, 1, 3, 1, 4, 6, 1, 1, 3, 4, 1, 4, 3, 2, 4, 6, 6]</pre>
+
+<p>Zanima nas, kolikokrat je padla katera številka. Nič lažjega. Najprej si
+ pripravimo seznam s sedmimi ničlami.</p>
+
+<pre>>>> kolikokrat = [0] * 7
+>>> kolikokrat
+[0, 0, 0, 0, 0, 0, 0]</pre>
+
+<p>Zdaj nam bo, recimo <code>kolikokrat[3]</code> povedal, kolikokrat je padla
+ trojka. (Čemu sedem? Saj ima kocka samo šest ploskev. Boste videli. Finta.)
+ Zdaj pa štejmo: pojdimo čez vse mete in povečujmo števce.</p>
+
+<pre>>>> for met in meti:
+... kolikokrat[met] += 1
+...
+>>> kolikokrat
+[0, 14, 12, 15, 27, 6, 26]</pre>
+
+<p>(Kje je finta? <code>kolikokrat[0]</code> bo pove, kolikokrat je padla ničla.
+Nikoli pač. Napišite isto reč s seznamom dolžine šest, ne sedem. Ni problem,
+ampak tako, kot smo nardili je preprostejše.)</p>
+
+<p>(Kocka je res videti nekoliko sumljiva: štirke in šestice so nekam pogoste
+ na račun petk. A pustimo statistikom vprašanje, ali je to še lahko
+ naključno ali ne.)</p>
+
+<p>Ups, nalogo smo rešili kar s seznamom! Da, to gre, ker so "predalčki"
+ seznama oštevilčeni, tako kot ploskve kocke. Tole pa ne bo šlo: tule je
+ seznam telefonskih številk deklet, ki jih je danes klical Benjamin. Katero
+ je poklical kolikokrat?</p>
+
+<pre>klici = ['041 103194', '040 193831', '040 318319', '040 193831', '041 310239',
+ '040 318319', '040 318319', '040 318319', '040 193831', '040 193831',
+ '040 193831', '040 193831', '040 193831', '040 318319', '040 318319',
+ '040 318319', '040 193831', '040 318319', '041 103194', '041 103194',
+ '041 310239', '040 193831', '041 103194', '041 310239', '041 310239',
+ '040 193831', '041 310239', '041 103194', '040 193831', '040 318319']</pre>
+
+<p>Za tako velike številke bi moral imeti seznam zelo veliko predalčkov. Še
+ huje bi bilo, če bi namesto seznama številk dobili seznam klicanih oseb.</p>
+
+<pre>klici = ['Cilka', 'Dani', 'Berta', 'Dani', 'Ana', 'Berta', 'Berta',
+ 'Berta', 'Dani', 'Dani', 'Dani', 'Dani', 'Dani', 'Berta', 'Berta',
+ 'Berta', 'Dani', 'Berta', 'Cilka', 'Cilka', 'Ana', 'Dani', 'Cilka',
+ 'Ana', 'Ana', 'Dani', 'Ana', 'Cilka', 'Dani', 'Berta']</pre>
+
+<p>Tu smo s seznamom pogoreli. No, ne čisto; rešitev s seznami seveda obstaja,
+ je pa zoprna - podobna je tistemu, kar smo v začetku počeli z bančnimi
+ računi.</p>
+
+<p>S slovarji je veliko lepše:
+<pre>pogostosti = {}
+for ime in klici:
+ if ime not in pogostosti:
+ pogostosti[ime] = 0
+ pogostosti[ime] += 1
+print(pogostosti)</pre>
+
+<p>Ob vsakem novem klicu preverimo, ali je klicano <code>ime</code> že v
+ slovarju. Če ga ni, da dodamo. Nato - najsibo ime novo ali ne - povečamo
+ števec klicev pri tej osebi.</p>
+
+
+<p>Ker je ta stvar resnično pogosta, nam Python pomaga z modulom
+<code>collections</code>, ki vsebuje podatkovni tip <code>defaultdict</code>.
+(Modul, ki vsebuje podatkovni tip?! Da, da; tudi to je ena od stvari, ki jih
+bomo našli v modulih.) Ta se obnaša tako kot slovar, z eno izjemo: če zahtevamo
+kak element, ki ne obstaja, si ga meni nič tebi nič izmisli. Točneje, doda ga v
+slovar in mu postavi privzeto vrednost. Katero, določimo. Pri tem ne podamo
+privzete vrednosti, temveč "funkcijo", ki bo vračala privzeto vrednost.
+<code>defaultdict</code> bo ustvarjal te, nove vrednosti tako, da bo poklical
+to funkcijo brez argumentov in kot privzeto vrednost uporabil, kar vrne
+funkcija, ki jo v ta namen vsakič sproti pokliče.</p>
+
+<p>Zelo pogosto bo privzeta vrednost 0 in funkcija, ki vrača
+ 0, se imenuje, hm, <code>int</code>.</p>
+
+<p>("Funkcija" <code>int</code>, je vedno sumljivejša in sumljivejša. Že od
+začetka smo v zvezi z njo dajali besedo "funkcija" pod narekovaje, zdaj pa vidimo,
+da zmore vedno več in več stvari. Pa še enako ji je ime kot tipu in funkcij, ki
+jim je ime enako kot tipom, je vedno več. Kakšna skrivnost se skriva za tem? To
+boste izvedeli v enem od prihodnjih napetih nadaljevanj Programiranja 1.)</p>
+
+<p>Preštejmo torej še enkrat Benjaminove klice, tokrat s slovarjem s privzetimi
+ vrednostmi.</p>
+
+<pre>import collections
+
+pogostosti = collections.defaultdict(int)
+for ime in klici:
+ pogostosti[ime] += 1</pre>
+
+<p>Ni to kul?</p>
+
+<p>Poglejmo si nekaj, kar je kul še bolj.</p>
+
+
+<h2>Števec</h2>
+
+<p>Preštevanje je pravzaprav tako pogosta reč, da obstaja zanj specializiran
+ tip. Tako kot <code>defaultdict</code> je v modulu <code>collections</code>,
+imenuje pa se <code>Counter</code>.</p>
+
+<pre>>>> stevilo_klicev = collections.Counter(klici)
+>>> stevilo_klicev
+Counter({'Dani': 11, 'Berta': 9, 'Cilka': 5, 'Ana': 5})</pre>
+
+<p>Komentirali ne bomo veliko, ker še ne znamo. Že ob tem, da sem temu rekel
+ tip, sem se moral ugrizniti v jezik, saj bi raje govoril o razredu. Kaj je
+ in kako deluje, pa nas še presega. Zna pa pravzaprav še veliko stvari,
+ tako da tem, ki jih zanima, priporočam, da si ga ogledajo. Mimogrede:</p>
+
+<pre>>>> napis = "CVIVS IN HOC RENOVATA LOCO PIA FVLGET IMAGO SIS" \
+ "CVSTOS POPVLI SANCTE IACOBE TVI"
+>>> collections.Counter()
+Counter({' ': 13, 'I': 8, 'O': 8, 'V': 7, 'A': 6, 'C': 6, 'S': 6, 'T': 5, 'E': 4,
+'L': 3, 'N': 3, 'P': 3, 'G': 2, 'B': 1, 'F': 1, 'H': 1, 'M': 1, 'R': 1})</pre>
+
+<p>Se pravi, da lahko kronogram rešimo tudi z</p>
+
+<pre>def kronogram(s):
+ crke = collections.Counter(s)
+ return crke["I"] + 5 * crke["V"] + 10 * crke["X"] + 50 * crke["L"] + \
+ 100 * crke["C"] + 500 * crke["D"] + 1000 * crke["M"]</pre>
+
+
+
+<h2>Množice</h2>
+
+<p>Množice so podobne seznamom, a s to razliko, da lahko vsebujejo vsak element
+ samo enkrat. Po drugi strani (in ne le po drugi strani, tudi tehnično) pa
+ so podobne slovarjem. Niso namreč urejene in vsebujejo lahko le elemente,
+ ki so nespremenljivi. Poleg tega pa lahko zelo hitro ugotovimo, ali množica
+ vsebuje določen element ali ne - tako kot lahko pri slovarjih hitro
+ ugotovimo, ali vsebujejo določen ključ ali ne.</p>
+
+<p>Množice zapišemo z zavitimi oklepaji, tako kot smo vajeni pri matematiki.</p>
+<pre>danasnji_klici = {"Ana", "Cilka", "Eva"}</pre>
+
+<p>Tako lahko sestavimo le neprazno množico. Če napišemo le oklepaj in
+ zaklepaj, <code>{}</code>, pa dobimo slovar. (Čemu so se odločili, naj bo
+ to slovar, ne množica? Slovar je bil prej, množice je Python dobil kasneje.
+ Zato.) Če hočemo narediti prazno množico, rečemo</p>
+
+<pre>prazna = set()</pre>
+
+<p>"Funkcija" <code>set</code> je malo podobna "funkciji" <code>int</code>:
+ damo ji lahko različne argumente, pa jih bo spremenila v množico. Damo ji
+ lahko, recimo, seznam, pa bomo dobili množico z vsemi elementi, ki se
+ pojavijo v njem.</p>
+
+<pre>>>> set([1, 2, 3])
+{1, 2, 3}
+>>> set(range(5))
+{0, 1, 2, 3, 4}
+>>> set([6, 42, 1, 3, 1, 1, 6])
+{1, 42, 3, 6}</pre>
+
+<p>Mimogrede opazimo, da tudi množice, tako kot slovarji, res ne dajo nič na
+ vrstni red.</p>
+
+<p>Poleg seznamov lahko množicam podtaknemo karkoli, prek česar bi lahko šli
+ z zanko <code>for</code>, recimo niz ali slovar.</p>
+
+<pre>>>> set("Benjamin")
+{'a', 'B', 'e', 'i', 'j', 'm', 'n'}
+>>> stevilke
+{'Dani': '040 193831', 'Fanči': '040 135367', 'Iva': '040 222333',
+'Helga': '+49 175 4728 475', 'Eva': '051 123123', 'Cilka': '041 103194',
+'Berta': '040 318319', 'Ana': '041 310239'}
+>>> set(stevilke)
+{'Iva', 'Helga', 'Eva', 'Berta', 'Fanči', 'Dani', 'Cilka', 'Ana'}</pre>
+
+<p>Spremenljivka <code>stevilke</code> (še vedno) vsebuje slovar, katerega
+ ključi so imena Benjaminovih oboževalk. Ker zanka prek slovarja "vrača"
+ ključe, bo tudi množica, ki jo sestavimo iz slovarja, vsebovala ključe.</p>
+
+<p>V množico lahko dodajamo elemente in vprašamo se lahko, ali množica vsebuje
+ določen element.</p>
+
+<pre>>>> s = set("Benjamin")
+>>> "e" in s
+True
+>>> "u" in s
+False
+>>> s.add("u")
+>>> s
+{'a', 'B', 'e', 'i', 'j', 'm', 'n', 'u'}
+>>> "u" in s
+True
+>>> s.add("a")
+>>> s.add("a")
+>>> s.add("a")
+>>> s
+{'a', 'B', 'e', 'i', 'j', 'm', 'n', 'u'}</pre>
+
+<p>Na koncu smo poskušali v množico dodati element, ki ga že vsebuje. To seveda
+ ne gre, množica vsak element vsebuje le enkrat.</p>
+
+<p>Če imamo dve množici, lahko izračunamo njuno unijo, presek, razliko...</p>
+
+<pre>>>> u = {1, 2, 3}
+>>> v = {3, 4, 5}
+>>> u | v
+{1, 2, 3, 4, 5}
+>>> u & v
+{3}</pre>
+
+<p>Preverimo lahko tudi, ali je neka množica podmnožica (ali nadmnožica druge).
+ To najpreprosteje storimo kar z operatorji za primerjanje.</p>
+
+<pre>>>> u = {1, 2, 3}
+>>> {1, 2} <= u
+True
+>>> {1, 2, 3, 4} <= u
+False
+>>> {1, 2, 3} <= u
+True
+>>> {1, 2, 3} < u
+False</pre>
+
+<p><code>{1, 2, 3}</code>, je podmnožica <code>u</code>-ja, ni pa njegove
+ <em>prava podmnožica</em>, saj vsebuje kar cel <code>u</code>.</p>
+
+<p>Z množicami je mogoče početi še marsikaj zanimivega - vendar bodi dovolj.</p>
+
+
+<h4>Primer: Seznami v slovarjih</h4>
+
+<p>Recimo, da želimo sestaviti skupine datotek v nekem direktoriju glede na
+ končnice. Sestavili bomo slovar, katerega ključi bodo končnice, na primer
+ .py, .txt in .mp3, vrednosti pri vsakem ključu pa imena datotek s to
+ končnico.</p>
+
+<pre>datoteke = {}
+for ime in os.listdir(dir):
+ konc = os.path.splitext(ime)[1]
+ if not konc in datoteke:
+ datoteke[konc] = set()
+ datoteke[konc].add(ime)</pre>
+
+
+<p>Če ste bili pozorni, niste spregledali, da je gornji program pravzaprav na
+ nek način enak programu, s katerim smo preštevali, kolikokrat je Benjamin
+ klical katero od deklet:
+
+<pre>pogostosti = {}
+for ime in klici:
+ if not ime in pogostosti:
+ pogostosti[ime] = 0
+ pogostosti[ime] += 1</pre>
+</p>
+
+<p>"Istost" je v tem, da obakrat naredimo prazen slovar. Nato gremo z zanko
+<code>for</code> čez neke reči. Za vsako reč (v gornjem primeru datoteke, v
+spodnjem ime dekleta) preverimo, ali je že v slovarju. Če je ni, jo dodamo tako,
+da ji damo neko privzeto vrednost (zgoraj prazno množico, spodaj 0). Nato
+pravkar dodano stvar posodobimo glede na trenutno reč (v gornjem primeru dodamo
+datoteko v pravkar sestavljeno ali od prej obstoječo množico, v spodnjem povečamo
+število klicev te osebe za 1).</p>
+
+<p>Aja, to finto pa že poznamo. Slovarji s privzetimi vrednostmi!</p>
+
+<pre>datoteke = collections.defaultdict(set)
+for ime in os.listdir(dir):
+ konc = os.path.splitext(ime)[1]
+ datoteke[konc].add(ime)</pre>
+</html>
\ No newline at end of file diff --git a/python/problems/re/common.py b/python/problems/re/common.py new file mode 100644 index 0000000..56b244e --- /dev/null +++ b/python/problems/re/common.py @@ -0,0 +1,2 @@ +id = 2009 +number = 9 diff --git a/python/problems/re/double_parentheses/common.py b/python/problems/re/double_parentheses/common.py new file mode 100644 index 0000000..5806559 --- /dev/null +++ b/python/problems/re/double_parentheses/common.py @@ -0,0 +1,69 @@ +import re +from python.util import has_token_sequence, string_almost_equal, \ + string_contains_number, get_tokens, get_numbers, get_exception_desc, \ + all_tokens, has_comprehension, has_loop, almost_equal, get_ast +from server.hints import Hint + +id = 20907 +number = 7 +visible = True + +solution = '''\ +import re + +def double_parentheses(s): + return re.findall(r'\(\(.*?\)\)', s) +''' + +hint_type = { + 'final_hint': Hint('final_hint') +} + +def test(python, code, aux_code=''): + func_name = 'double_parentheses' + tokens = get_tokens(code) + if not has_token_sequence(tokens, ['def', func_name]): + return False, [{'id' : 'no_func_name', 'args' : {'func_name' : func_name}}] + + in_out = [ + ('(a a) bb (cc)', []), + ('a a b b c c', []), + ('', []), + ('(()())(()', ['(()())']), + ('((aa (aa) aa)) bb ((cc)) (dd)', ['((aa (aa) aa))', '((cc))']), + ] + + test_in = [('{0}("{1}")'.format(func_name, str(l[0])), None) + for l in in_out] + test_out = [l[1] for l in in_out] + + answers = python(code=aux_code+code, inputs=test_in, timeout=1.0) + n_correct = 0 + tin, tout = None, None + for i, (ans, to) in enumerate(zip(answers, test_out)): + res = ans[0] + corr = res == to + n_correct += corr + if not corr: + tin = test_in[i][0] + tout = to + + passed = n_correct == len(test_in) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_in)}}] + if tin: + hints.append({'id': 'problematic_test_case', 'args': {'testin': str(tin), 'testout': str(tout)}}) + if passed: + hints.append({'id': 'final_hint'}) + + return passed, hints + + +def hint(python, code, aux_code=''): + tokens = get_tokens(code) + + # run one test first to see if there are any exceptions + answer = python(code=aux_code+code, inputs=[(None, None)], timeout=1.0) + exc = get_exception_desc(answer[0][3]) + if exc: return exc + + return None diff --git a/python/problems/re/double_parentheses/en.py b/python/problems/re/double_parentheses/en.py new file mode 100644 index 0000000..57f27f1 --- /dev/null +++ b/python/problems/re/double_parentheses/en.py @@ -0,0 +1,13 @@ +id = 20907 +name = 'Double parentheses' + +description = '''\ +<p>(translation missing)</p>''' + +hint = { + 'plan': '''\ +<p>(translation missing)</p>''', + + 'no_input_call': '''\ +<p>(translation missing)</p>''', +} diff --git a/python/problems/re/double_parentheses/sl.py b/python/problems/re/double_parentheses/sl.py new file mode 100644 index 0000000..501bce7 --- /dev/null +++ b/python/problems/re/double_parentheses/sl.py @@ -0,0 +1,24 @@ +import server +mod = server.problems.load_language('python', 'sl') + +id = 20907 +name = 'Dvojni oklepaji' + +description = '''\ +<p> +Napišite funkcijo <code>double_parantheses(s)</code>, ki vrne vse nize, ki se +pojavijo v dvojnih oklepajih.</p> +<pre> +>>> double_parentheses('((aa (aa) aa)) bb ((cc)) (dd)') +['((aa (aa) aa))', '((cc))'] +</pre> +''' + +plan = [] + +hint = { + 'final_hint': ['''\ +<p>Program je pravilen! <br> +</p> +'''], +} diff --git a/python/problems/re/en.py b/python/problems/re/en.py new file mode 100644 index 0000000..247e5bb --- /dev/null +++ b/python/problems/re/en.py @@ -0,0 +1,2 @@ +name = 'Regular expressions' +description = 'Regular expressions exercises' diff --git a/python/problems/re/intnum/common.py b/python/problems/re/intnum/common.py new file mode 100644 index 0000000..8e8a92e --- /dev/null +++ b/python/problems/re/intnum/common.py @@ -0,0 +1,74 @@ +import re +from python.util import has_token_sequence, string_almost_equal, \ + string_contains_number, get_tokens, get_numbers, get_exception_desc, \ + all_tokens, has_comprehension, has_loop, almost_equal, get_ast +from server.hints import Hint + +id = 20903 +number = 3 +visible = True + +solution = '''\ +def num(s): + res = re.search('-?[0-9]+', s) + if not res: + return None + return int(res.group(0)) +''' + +hint_type = { + 'final_hint': Hint('final_hint') +} + +def test(python, code, aux_code=''): + func_name = 'num' + tokens = get_tokens(code) + if not has_token_sequence(tokens, ['def', func_name]): + return False, [{'id' : 'no_func_name', 'args' : {'func_name' : func_name}}] + + in_out = [ + ('abc', None), + ('1', 1), + ('111', 111), + ('12 1', 12), + ('5-112', 5), + ('m-221', -221), + ('Rač. 356', 356), + ('Ulica št. 15a', 15), + ('Višina 180, teža 76', 180), + ] + + test_in = [('{0}("{1}")'.format(func_name, str(l[0])), None) + for l in in_out] + test_out = [l[1] for l in in_out] + + answers = python(code=aux_code+code, inputs=test_in, timeout=1.0) + n_correct = 0 + tin, tout = None, None + for i, (ans, to) in enumerate(zip(answers, test_out)): + res = ans[0] + corr = res == to + n_correct += corr + if not corr: + tin = test_in[i][0] + tout = to + + passed = n_correct == len(test_in) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_in)}}] + if tin: + hints.append({'id': 'problematic_test_case', 'args': {'testin': str(tin), 'testout': str(tout)}}) + if passed: + hints.append({'id': 'final_hint'}) + + return passed, hints + + +def hint(python, code, aux_code=''): + tokens = get_tokens(code) + + # run one test first to see if there are any exceptions + answer = python(code=aux_code+code, inputs=[(None, None)], timeout=1.0) + exc = get_exception_desc(answer[0][3]) + if exc: return exc + + return None diff --git a/python/problems/re/intnum/en.py b/python/problems/re/intnum/en.py new file mode 100644 index 0000000..33e0bd1 --- /dev/null +++ b/python/problems/re/intnum/en.py @@ -0,0 +1,13 @@ +id = 20903 +name = 'Signed integer' + +description = '''\ +<p>(translation missing)</p>''' + +hint = { + 'plan': '''\ +<p>(translation missing)</p>''', + + 'no_input_call': '''\ +<p>(translation missing)</p>''', +} diff --git a/python/problems/re/intnum/sl.py b/python/problems/re/intnum/sl.py new file mode 100644 index 0000000..8e4d8a6 --- /dev/null +++ b/python/problems/re/intnum/sl.py @@ -0,0 +1,27 @@ +import server +mod = server.problems.load_language('python', 'sl') + +id = 20903 +name = 'Predznačeno celo število' + +description = '''\ +<p> +Napišite funkcijo <code>num(s)</code>, ki vrne prvo celo predznačeno število +v nizu. Število naj bo tipa int. Če števila v nizu ni, naj funkcija vrne +<code>None</code>.</p> +<pre> +>>> num('Večna pot 113') +113 +>>> num('a-20=22') +-20 +</pre> +''' + +plan = [] + +hint = { + 'final_hint': ['''\ +<p>Program je pravilen! <br> +</p> +'''], +} diff --git a/python/problems/re/num/common.py b/python/problems/re/num/common.py new file mode 100644 index 0000000..4c2f0bf --- /dev/null +++ b/python/problems/re/num/common.py @@ -0,0 +1,75 @@ +import re +from python.util import has_token_sequence, string_almost_equal, \ + string_contains_number, get_tokens, get_numbers, get_exception_desc, \ + all_tokens, has_comprehension, has_loop, almost_equal, get_ast +from server.hints import Hint + +id = 20904 +number = 4 +visible = True + +solution = '''\ +def num(s): + res = re.search(r'-?((\d*\.\d+)|(\d+\.?))([Ee][+-]\d*)?', s) + if not res: + return None + return int(res.group(0)) +''' + +hint_type = { + 'final_hint': Hint('final_hint') +} + +def test(python, code, aux_code=''): + func_name = 'num' + tokens = get_tokens(code) + if not has_token_sequence(tokens, ['def', func_name]): + return False, [{'id' : 'no_func_name', 'args' : {'func_name' : func_name}}] + + in_out = [ + ('abc', None), + ('1', 1.0), + ('1.1', 1.1), + ('111e+2', 111e+2), + ('12-1', 12.0), + ('5.123-112', 5.123), + ('m-221.0', -221.0), + ('Rač. +356', 356.0), + ('Ulica št. 15a', 15), + ('Višina 180.42, teža 76.5', 180.42), + ] + + test_in = [('{0}("{1}")'.format(func_name, str(l[0])), None) + for l in in_out] + test_out = [l[1] for l in in_out] + + answers = python(code=aux_code+code, inputs=test_in, timeout=1.0) + n_correct = 0 + tin, tout = None, None + for i, (ans, to) in enumerate(zip(answers, test_out)): + res = ans[0] + corr = res == to + n_correct += corr + if not corr: + tin = test_in[i][0] + tout = to + + passed = n_correct == len(test_in) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_in)}}] + if tin: + hints.append({'id': 'problematic_test_case', 'args': {'testin': str(tin), 'testout': str(tout)}}) + if passed: + hints.append({'id': 'final_hint'}) + + return passed, hints + + +def hint(python, code, aux_code=''): + tokens = get_tokens(code) + + # run one test first to see if there are any exceptions + answer = python(code=aux_code+code, inputs=[(None, None)], timeout=1.0) + exc = get_exception_desc(answer[0][3]) + if exc: return exc + + return None diff --git a/python/problems/re/num/en.py b/python/problems/re/num/en.py new file mode 100644 index 0000000..0feb5ff --- /dev/null +++ b/python/problems/re/num/en.py @@ -0,0 +1,13 @@ +id = 20904 +name = 'Signed any number (integer, float)' + +description = '''\ +<p>(translation missing)</p>''' + +hint = { + 'plan': '''\ +<p>(translation missing)</p>''', + + 'no_input_call': '''\ +<p>(translation missing)</p>''', +} diff --git a/python/problems/re/num/sl.py b/python/problems/re/num/sl.py new file mode 100644 index 0000000..cf96b5a --- /dev/null +++ b/python/problems/re/num/sl.py @@ -0,0 +1,29 @@ +import server +mod = server.problems.load_language('python', 'sl') + +id = 20904 +name = 'Predznačeno poljubno število' + +description = '''\ +<p> +Napišite funkcijo <code>num(s)</code>, ki vrne prvo poljubno predznačeno število +(lahko ima tudi decimalko). Število naj bo tipa <code>float</code>. Če števila v nizu ni, +naj funkcija vrne <code>None</code>.</p> +<pre> +>>> num('Večna pot 113') +113 +>>> num('a-20.5=21.5') +-20.5 +>>> num('Pregledali smo 10e+5 elementov.') +10e+5 +</pre> +''' + +plan = [] + +hint = { + 'final_hint': ['''\ +<p>Program je pravilen! <br> +</p> +'''], +} diff --git a/python/problems/re/ones/common.py b/python/problems/re/ones/common.py new file mode 100644 index 0000000..851a53f --- /dev/null +++ b/python/problems/re/ones/common.py @@ -0,0 +1,69 @@ +import re +from python.util import has_token_sequence, string_almost_equal, \ + string_contains_number, get_tokens, get_numbers, get_exception_desc, \ + all_tokens, has_comprehension, has_loop, almost_equal, get_ast +from server.hints import Hint + +id = 20901 +number = 1 +visible = True + +solution = '''\ +def ones(s): + return re.findall(r'1+', s) +''' + +hint_type = { + 'final_hint': Hint('final_hint') +} + +def test(python, code, aux_code=''): + func_name = 'ones' + tokens = get_tokens(code) + if not has_token_sequence(tokens, ['def', func_name]): + return False, [{'id' : 'no_func_name', 'args' : {'func_name' : func_name}}] + + in_out = [ + ('', []), + ('111', ['111']), + ('121', ['1','1']), + ('112', ['11']), + ('221', ['1']), + ('222', []), + ('12211222111', ['1','11','111']), + ] + + test_in = [('{0}("{1}")'.format(func_name, str(l[0])), None) + for l in in_out] + test_out = [l[1] for l in in_out] + + answers = python(code=aux_code+code, inputs=test_in, timeout=1.0) + n_correct = 0 + tin, tout = None, None + for i, (ans, to) in enumerate(zip(answers, test_out)): + res = ans[0] + corr = res == to + n_correct += corr + if not corr: + tin = test_in[i][0] + tout = to + + passed = n_correct == len(test_in) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_in)}}] + if tin: + hints.append({'id': 'problematic_test_case', 'args': {'testin': str(tin), 'testout': str(tout)}}) + if passed: + hints.append({'id': 'final_hint'}) + + return passed, hints + + +def hint(python, code, aux_code=''): + tokens = get_tokens(code) + + # run one test first to see if there are any exceptions + answer = python(code=aux_code+code, inputs=[(None, None)], timeout=1.0) + exc = get_exception_desc(answer[0][3]) + if exc: return exc + + return None diff --git a/python/problems/re/ones/en.py b/python/problems/re/ones/en.py new file mode 100644 index 0000000..615e777 --- /dev/null +++ b/python/problems/re/ones/en.py @@ -0,0 +1,13 @@ +id = 20901 +name = 'Repeated 1s' + +description = '''\ +<p>(translation missing)</p>''' + +hint = { + 'plan': '''\ +<p>(translation missing)</p>''', + + 'no_input_call': '''\ +<p>(translation missing)</p>''', +} diff --git a/python/problems/re/ones/sl.py b/python/problems/re/ones/sl.py new file mode 100644 index 0000000..91f6ec0 --- /dev/null +++ b/python/problems/re/ones/sl.py @@ -0,0 +1,26 @@ +import server +mod = server.problems.load_language('python', 'sl') + +id = 20901 +name = 'Ponovitve znaka' + +description = '''\ +<p> +Napišite funkcijo <code>ones(s)</code>, ki vrne seznam vseh podnizov, ki +vsebujejo eno ali več ponovitev znaka 1. </p> +<pre> +>>> ones('12211222111') +['1', '11', '111'] +>>> ones('') +[] +</pre> +''' + +plan = [] + +hint = { + 'final_hint': ['''\ +<p>Program je pravilen! <br> +</p> +'''], +} diff --git a/python/problems/re/ones3/common.py b/python/problems/re/ones3/common.py new file mode 100644 index 0000000..4b151e8 --- /dev/null +++ b/python/problems/re/ones3/common.py @@ -0,0 +1,70 @@ +import re +from python.util import has_token_sequence, string_almost_equal, \ + string_contains_number, get_tokens, get_numbers, get_exception_desc, \ + all_tokens, has_comprehension, has_loop, almost_equal, get_ast +from server.hints import Hint + +id = 20902 +number = 2 +visible = True + +solution = '''\ +def ones(s): + return re.findall(r'1{3,}', s) +''' + +hint_type = { + 'final_hint': Hint('final_hint') +} + +def test(python, code, aux_code=''): + func_name = 'ones' + tokens = get_tokens(code) + if not has_token_sequence(tokens, ['def', func_name]): + return False, [{'id' : 'no_func_name', 'args' : {'func_name' : func_name}}] + + in_out = [ + ('', []), + ('111', ['111']), + ('121', []), + ('112', []), + ('221', []), + ('222', []), + ('12211222111', ['111']), + ('211111112211222111', ['1111111', '111']), + ] + + test_in = [('{0}("{1}")'.format(func_name, str(l[0])), None) + for l in in_out] + test_out = [l[1] for l in in_out] + + answers = python(code=aux_code+code, inputs=test_in, timeout=1.0) + n_correct = 0 + tin, tout = None, None + for i, (ans, to) in enumerate(zip(answers, test_out)): + res = ans[0] + corr = res == to + n_correct += corr + if not corr: + tin = test_in[i][0] + tout = to + + passed = n_correct == len(test_in) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_in)}}] + if tin: + hints.append({'id': 'problematic_test_case', 'args': {'testin': str(tin), 'testout': str(tout)}}) + if passed: + hints.append({'id': 'final_hint'}) + + return passed, hints + + +def hint(python, code, aux_code=''): + tokens = get_tokens(code) + + # run one test first to see if there are any exceptions + answer = python(code=aux_code+code, inputs=[(None, None)], timeout=1.0) + exc = get_exception_desc(answer[0][3]) + if exc: return exc + + return None diff --git a/python/problems/re/ones3/en.py b/python/problems/re/ones3/en.py new file mode 100644 index 0000000..74c9dd5 --- /dev/null +++ b/python/problems/re/ones3/en.py @@ -0,0 +1,13 @@ +id = 20902 +name = 'Three times repeated 1s' + +description = '''\ +<p>(translation missing)</p>''' + +hint = { + 'plan': '''\ +<p>(translation missing)</p>''', + + 'no_input_call': '''\ +<p>(translation missing)</p>''', +} diff --git a/python/problems/re/ones3/sl.py b/python/problems/re/ones3/sl.py new file mode 100644 index 0000000..1d571c6 --- /dev/null +++ b/python/problems/re/ones3/sl.py @@ -0,0 +1,24 @@ +import server +mod = server.problems.load_language('python', 'sl') + +id = 20902 +name = 'Ponovitve znaka 3x' + +description = '''\ +<p> +Napišite funkcijo <code>ones(s)</code>, ki vrne seznam vseh podnizov, ki +vsebujejo tri ali več ponovitev znaka 1. </p> +<pre> +>>> ones('12211222111') +['111'] +</pre> +''' + +plan = [] + +hint = { + 'final_hint': ['''\ +<p>Program je pravilen! <br> +</p> +'''], +} diff --git a/python/problems/re/parentheses/common.py b/python/problems/re/parentheses/common.py new file mode 100644 index 0000000..7144bd8 --- /dev/null +++ b/python/problems/re/parentheses/common.py @@ -0,0 +1,69 @@ +import re +from python.util import has_token_sequence, string_almost_equal, \ + string_contains_number, get_tokens, get_numbers, get_exception_desc, \ + all_tokens, has_comprehension, has_loop, almost_equal, get_ast +from server.hints import Hint + +id = 20906 +number = 6 +visible = True + +solution = '''\ +import re + +def parentheses(s): + return re.findall(r'\([^)]+\)', s) +''' + +hint_type = { + 'final_hint': Hint('final_hint') +} + +def test(python, code, aux_code=''): + func_name = 'parentheses' + tokens = get_tokens(code) + if not has_token_sequence(tokens, ['def', func_name]): + return False, [{'id' : 'no_func_name', 'args' : {'func_name' : func_name}}] + + in_out = [ + ('(a a) bb (cc)', ['(a a)', '(cc)']), + ('a a b b c c', []), + ('', []), + ('To niso oklepaji (to so), (ta je pa pokvarjen', ['(to so)']), + ('()())(()', ['()', '()', '(()']), + ] + + test_in = [('{0}("{1}")'.format(func_name, str(l[0])), None) + for l in in_out] + test_out = [l[1] for l in in_out] + + answers = python(code=aux_code+code, inputs=test_in, timeout=1.0) + n_correct = 0 + tin, tout = None, None + for i, (ans, to) in enumerate(zip(answers, test_out)): + res = ans[0] + corr = res == to + n_correct += corr + if not corr: + tin = test_in[i][0] + tout = to + + passed = n_correct == len(test_in) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_in)}}] + if tin: + hints.append({'id': 'problematic_test_case', 'args': {'testin': str(tin), 'testout': str(tout)}}) + if passed: + hints.append({'id': 'final_hint'}) + + return passed, hints + + +def hint(python, code, aux_code=''): + tokens = get_tokens(code) + + # run one test first to see if there are any exceptions + answer = python(code=aux_code+code, inputs=[(None, None)], timeout=1.0) + exc = get_exception_desc(answer[0][3]) + if exc: return exc + + return None diff --git a/python/problems/re/parentheses/en.py b/python/problems/re/parentheses/en.py new file mode 100644 index 0000000..035938e --- /dev/null +++ b/python/problems/re/parentheses/en.py @@ -0,0 +1,13 @@ +id = 20906 +name = 'Parentheses' + +description = '''\ +<p>(translation missing)</p>''' + +hint = { + 'plan': '''\ +<p>(translation missing)</p>''', + + 'no_input_call': '''\ +<p>(translation missing)</p>''', +} diff --git a/python/problems/re/parentheses/sl.py b/python/problems/re/parentheses/sl.py new file mode 100644 index 0000000..bdd6034 --- /dev/null +++ b/python/problems/re/parentheses/sl.py @@ -0,0 +1,24 @@ +import server +mod = server.problems.load_language('python', 'sl') + +id = 20906 +name = 'Oklepaji' + +description = '''\ +<p> +Napišite funkcijo <code>parantheses(s)</code>, ki vrne vse nize, ki se pojavijo +v oklepajih.</p> +<pre> +>>> parentheses('(a a) bb (cc)') +['(a a)', '(cc)'] +</pre> +''' + +plan = [] + +hint = { + 'final_hint': ['''\ +<p>Program je pravilen! <br> +</p> +'''], +} diff --git a/python/problems/re/re_sl.html b/python/problems/re/re_sl.html new file mode 100644 index 0000000..4432336 --- /dev/null +++ b/python/problems/re/re_sl.html @@ -0,0 +1,371 @@ +<!DOCTYPE html>
+<html lang="sl">
+<head>
+<meta charset="utf-8" />
+<title></title>
+<link rel="stylesheet" type="text/css" href="/css/codeq.css" />
+<link rel="stylesheet" type="text/css" href="../../style.css" />
+</head>
+<body>
+
+<h2>Regularni izrazi</h2>
+
+<p>Ena najbolj uporabnih stvari v skriptnih jezikih so "regularni izrazi". Če jih boste znali, boste glavni frajerji in boste lahko delali odlične reči. Po drugi strani pa niso prav nič zapleteni, čisto enostavno se jih je naučiti ... in, no, priznam, kar zoprno sestavljati. Ko sestavljamo kaj bolj zapletenega, se tudi stari mački nekoliko zafrkavamo, preden jih spravimo v delujoče stanje.</p>
+
+<h3>Regularni izraz</h3>
+
+<p>Če bi hoteli teoretizirati, bi rekli, da opisujejo <em>jezik</em>, pri čemer z "jezikom" mislimo nekakšno množico nizov. So tudi v tesnem sorodu s končnimi avtomati, ki vam morda niso več neznani, če kaj sledite Uvodu v računalništvo.) A pustimo teorijo in uvode. Naučili se bomo pisati nize, s katerimi opišemo nekakšne vzorce. Recimo, da imamo na voljo naslednje oznake, ki jih lahko tlačimo v nize:
+<dl>
+<dt>.</dt><dd>katerikoli znak</dd>
+<dt>\w</dt><dd>katerakoli črka ali števka</dd>
+<dt>\W</dt><dd>katerikoli znak, ki ni črka ali števka</dd>
+<dt>\d</dt><dd>katerakoli števka</dd>
+<dt>\D</dt><dd>katerakoli znak, ki ni števka</dd>
+<dt>\s</dt><dd>beli prostor (presledek, tabulator, nova vrstica...)</dd>
+<dt>\S</dt><dd>karkoli, kar ni beli prostor</dd>
+<dt>^</dt><dd>začetek niza</dd>
+<dt>$</dt><dd>konec niza</dd><
+<dt>\</dt><dd>vzvratno poševnico uporabimo, ko bi radi v vzorec vpisali, recimo, piko. Znak <code>.</code> ima namreč poseben pomen (glej zgoraj), zato takrat, kadar v resnici želimo piko, napišemo <code>\.</code>. Enako velja za zvezdico, vprašaj, plus... in tudi vzvratno poševnico.</dd>
+<dt>oglati oklepaji</dt><dd>mednje napišemo seznam znakov in opisujejo katerikoli znak s tega seznama; če je prvi znak <code>^</code>, pa to pomeni katerikoli znak, ki ni na tem seznamu. Znotraj oglatih oklepajev je pika samo pika in celo vzvratna poševnica samo vzvratna poševnica.</dd>
+</dl>
+Poglejmo nekaj preprostih primerov vzorcev
+<dl>
+<dt>l.pa</dt><dd>Ta izraz opisuje "l", ki mu sledi karkoli in nato "pa". To je lahko "lipa", "lepa" ali "lopa", pa tudi "lrpa", "lmpa", "lgpa". In celo "l3pa", "l/pa" in "l pa". Vzorcu pa ne ustrezata, recimo, "sipa", saj se ne začne z "l" ali "lotupa", saj ima med "l" in "pa" več kot eno samo črko.</dd>
+<dt>l.p.</dt><dd>Tole pa je lahko tudi "lipi" ali "lopi" ali "lspt" ali "l1p4" ali "l-p)"... Torej vse besede s štirimi znaki, pri čemer mora biti prvi znak "l", tretji pa "p".</dd>
+<dt>l\wp\w</dt><dd>Ta vzorec je podoben prejšnjemu, le da zahteva, da sta drugi in četrti znak črki ali številki. "lipa" in "l1p4" sta še vedno spremenljivi, "l-p)" pa ne več.</dd>
+<dt>lip[aieo]</dt><dd>Tole pa je lahko "lipa", "lipi", "lipe" ali "lipo". Ne pa "lipu" ali "lipm".</dd>
+<dt>lip[^u t]</dt><dd>Ta vzorec opisuje "lip", ki mu sledi katerikoli znak, razen u-ja, presledka ali t-ja.</dd>
+<dt>s[tpr]anje</dt><dd>"stanje", "spanje" in nekatere druge reči.</dd>
+</dl>
+Zdaj pa še par posebnih znakov.
+<dl>
+<dt>okrogli oklepaji</dt><dd>Z okroglimi oklepaji označimo skupino</dd>
+<dt>*</dt><dd>Z zvezdico označimo, da se sme prejšnji znak ali prejšnja skupina (ki smo jo zaprli v okrogle oklepaje) poljubnokrat ponoviti - lahko tudi ničkrat.</dd>
+<dt>+</dt><dd>Plus pomeni isto kot zvezdica, le da zahteva, da se prejšnja skupina ali znak pojavi vsaj enkrat.</dd>
+<dt>?</dt><dd>Z vprašajem povemo, naj se prejšnja skupina pojavi enkrat ali nobenkrat.</dd>
+</dl>
+Spet si oglejmo nekaj primerov.
+<dl>
+<dt>Go*l!</dt><dd>Ta vzorec opisuje nize, kot so "Gol!", "Gooooooool!" in "Goooooooooooooooool!". Žal pa tudi "Gl!". :)</dd>
+<dt>Go+l!</dt><dd>Popravljena verzija gornjega: zahteva, da se "o" pojavi vsaj enkrat.</dd>
+<dt>Go+l?!</dt><dd>Tole pa je podobno kot zgoraj, le da se črka <code>l</code> lahko pojavi ali pa tudi ne. To je torej lahko karkoli od tega, kar smo našteli zgoraj, poleg tega pa tudi "Go!" ali "Gooooooooooo!" </dd>
+</dl>
+Pa še par bolj zapletenih.
+<dl>
+<dt>(brm)+</dt><dd>Opisuje motor, ki dela "brmbrmbrmbrmbrmbrm" ali "brmbrmbrmbrmbrmbrmbrmbrm" ali vsaj "brm".</dd>
+<dt>ra(ta)*</dt><dd>Opisuje mitraljez (ki je lahko tudi pokvarjen in reče samo "ra" in razpade)</dd>
+<dt>tr([aeiou]l)*[aeiou]</dt><dd>Opisuje prepevanje, ko ne poznamo besedila ("tralalelalalalilolololu")</dd>
+<dt>jodl(dodl)*dii</dt><dd>Jodlanje (in jodldodlanje in jodldodldodldodldodlanje).</dd>
+<dt>jodl(dodl)?dii</dt><dd>Tole pa je samo jodlanje in jodldodlanje, ne pa tudi jodldodldodldodldodlanje.</dd>
+</dl>
+Pri plusu, zvezdici in vprašaju nas včasih zmoti, da požrejo preveč. Če hočemo, da požrejo čim manj, jim dodamo vprašaj.
+<dl>
+<dt>*?, +?, ??</dt><dd>Če k zvezdici, plusu ali vprašaju dodamo še en vprašaj, zahtevamo, naj ta zvezdica, plus ali vprašaj "požre" čim manj znakov. Tole je na prvi pogled zelo praktično; v resnici bo začetnik, ko odkrije te vprašaje, naredil veliko naivnih napak, zato se jim, vsaj v začetku, raje izognite in poskusite to, kar želite povedati, povedati brez njih.</dd>
+</dl>
+Vzemimo stavek "Ta nas Janez je en dolgocasnez, prav res". Če poskusimo v tem stavku poiskati podniz, ki ustreza vzorcu "J.*nez", bomo izvedeli, da "Janez je en dolgocasnez", kar ni ne lepo ne prav in nikakor ni tisto, kar smo hoteli. Težava je v tem, da je zvezdica požrla "nez je en dolgocas"; izraz namreč pravi, da hočemo "Ja", potem (karseda veliko) drugih črk in potem "nez". Če pa poiščemo "J.*?nez", bomo dobili samo "Janez", saj bo ".*" požrlo le toliko znakov, kolikor jih potrebujemo, da je iskanje uspešno, torej, v tem primeru, le črko "a".</p>
+
+<h3>Funkcije za uporabo regularnih izrazov</h3>
+
+<p>Naučili smo se opisovati vzorce. Kaj pa lahko s temi opisi počnemo?</p>
+
+<p>Regularni izrazi, ki smo jih spoznali, so bolj ali manj enaki v vseh jezikih. Jeziki, ki imajo veliko opraviti z iskanjem po besedilih, na primer php in javascript, imajo regularne izraze tesno vdelane v jezik - tako kot ima Python vdelana števila in nize (za nize nimamo "posebnega modula", ki bi ga morali uvoziti, preden lahko delamo z njimi), imajo ti jeziki vdelane tudi regularne izraze. Ne da bi potrebovali poseben modul, lahko preverimo, ali ima določen niz določeno obliko, podano z regularnim izrazom. Python je splošnejši jezik, zato regularnih izrazov ne sili v ospredju, temveč so lepo v posebnem modulu. Imenuje se <code>re</code>, kar je okrajšava za <em>regular expression</em>.</p>
+
+<p>Za primer uporabimo rdečo nit predavanj izpred nekaj let: v besedilu iščemo imena potencialnih teroristov, pri čemer so nam sumljive kar vse besede, ki se začnejo z veliko črko. Besedilo nekega prestreženega elektronskega sporočila je bilo takšno:
+<pre>msg = """Dragi Ahmed,
+
+kako si kaj? Upam, da so otroci ze zdravi.
+
+Mustafa, Osama in jaz smo se sli danes malo razgledat in
+kaze kar dobro. Abdulah pa ni mogel zraven, je sel v Pesavar
+prodat se tri kamele. Osama sicer pravi, da se mu to pred
+zimo ne splaca, ampak saj ves, kaksen je Abdulah. Harun in
+on, nic jima ne dopoves, se Osama ne. Jibril me klice,
+moram iti; oglasi se kaj na Skype!
+
+tvoj Husein
+
+"""</pre>
+
+<p>Vzorec, ki bo predstavljal ime - nekaj, kar se začne z veliko črko in nadaljuje s vsaj eno črko, je <code>[A-Z]\w+</code>.</p>
+
+<p>Najpreprostejša za uporabo (a ne tudi najbolj uporabna) funkcija, na kateri lahko poženemo ta izraz, je <code>findall</code>, ki, kot bi bili ob imenu nemara presenečeni (posebej, če ne bi znali angleško) poišče vse (neprekrivajoče se) podnize, ki jim ustreza izraz. Če imamo sporočilo shranjeno v nizu <code>msg</code>, bomo seznam imen dobili z
+<pre>>>> re.findall(r"[A-Z]\w+", msg)
+['Dragi', 'Ahmed', 'Upam', 'Mustafa', 'Osama', 'Abdulah', 'Pesavar', 'Osama', 'Abdulah', 'Harun', 'Osama', 'Jibril', 'Skype', 'Husein', 'PS', 'Python']</pre></p>
+
+<p>Opazka: pred narekovaj sem dodal <code>r</code>. Iz davnega začetka semestra se morda spomnite, kakšne posledice ima to: vse vzvratne poševnice (po domače: <em>backslashi</em>) po tem pomenijo le vzvratne poševnice, ne ubežnih zaporedij (torej, <code>\n</code> pomeni vzvratno poševnico in <code>n</code>, ne pa prehoda v novo vrsto). V regularnih izrazih se bo vedno trlo vzvrtanih poševnic, medtem ko bodo nove vrste in tabulatorji strašno redki, zato bomo skoraj vedno uporabljali nize z <code>r</code>-jem pred narekovaji.</p>
+
+<p>Metoda <code>findall</code> je najpreprostejša, ker vrne kar (pod)nize, ki ustrezajo vzorcu. Druge funkcije vračajo objekt, iz katerega lahko razberemo še kaj več. Vzemimo <code>search</code>, ki poišče prvo pojavitev vzorca.</p>
+
+<pre>>>> mo_ime = re.search(r"[A-Z]\w+", msg)
+>>> mo_ime
+<_sre.SRE_Match object at 0x045812F8></pre>
+
+<p><code>mo_ime</code> ni niz, tako kot prej, temveč objekt, ki vsebuje podatke o podnizu, ki se ujema z izrazom. (Ker gre za "<code>Match object</code>", zato sem mu pripel <code>mo_</code>. To spet spominja na madžarsko notacijo, ki sem jo omenil pri Qtju in tudi tu izvira iz (mojih) izkušenj: če imaš spremenljivko, ki predstavlja objekt, ki se nanaša na najdeno ime, boš imel najbrž tudi spremenljivko, ki bo vsebovala niz z najdenim imenom in imeli bosta tendenco, da se bosta enaki. Če prvemu vedno pripnemo <code>mo_</code>, je problem rešen.</p>)
+
+<pre>>>> mo_ime.group()
+'Dragi'
+>>> mo_ime.start()
+0
+>>> mo_ime.end()
+5
+>>> mo_ime.span()
+(0, 5)</pre>
+
+<p>Tule <code>start</code> pove indeks črke v nizu, kjer se ujemanje začne in <code>end</code> indeks črke za ujetim podnizom; <code>span</code> pove oboje hkrati. Najdeni niz se torej nahaja v <code>msg[0:5]</code>. Razlog, da se metoda, ki vrne celotni podniz, imenuje <code>group()</code>, pa bo postal jasen malo kasneje, ko se bomo pogovarjali o skupinah.</p>
+
+<p>Če želimo poiskati vse pojavitve vzorca in jih obdelati v zanki, bomo uporabili <code>finditer</code>, kot recimo tule:
+<pre>for s in re.finditer(r"[A-Z]\w+", msg):
+ print(s.group())</pre></p>
+
+<p>Metoda <code>sub</code> je podobna metodi <code>replace</code>, ki smo jo spoznali pri nizih: z njo lahko zamenjamo vse pojavitve danega vzorca s čim drugim.</p>
+
+<pre>>>> print(re.sub(r"[A-Z]\w+", "XXX", msg))
+XXX XXX,
+
+kako si kaj? XXX, da so otroci ze zdravi.
+
+XXX, XXX in jaz smo se sli danes malo razgledat in
+kaze kar dobro. XXX pa ni mogel zraven, je sel v XXX
+prodat se tri kamele. XXX sicer pravi, da se mu to pred
+zimo ne splaca, ampak saj ves, kaksen je XXX. XXX in
+on, nic jima ne dopoves, se XXX ne. XXX me klice,
+moram iti; oglasi se kaj na XXX!
+
+tvoj XXX
+</pre>
+
+<p>V zvezi s <code>sub</code> lahko opazimo, da so argumenti funkcije obrnjeni nekoliko drugače kot pri <code>replace</code>: <code>replace</code> je metoda razreda <code>str</code>, torej je niz, katerega podnize želimo spreminjati že implicitno podan (kot <code>self</code>), povedati je potrebno le, kakšen podniz zamenjamo s katerim. Za primer: na nekaterih operacijskih sistemih so presledki v imenih datotek precej tečna reč. Kako bi se jih znebili? Če bi poznali le nize, bi rekli
+
+<pre>fname = fname.replace(" ", "_")</pre>
+
+Metodi <code>sub</code> pa kot argument povemo vzorec, niz, po katerem naj išče in s čim naj zamenjuje. Če bi želeli gornjo zamenjavo opraviti z regularnimi izrazi, bi napisali:
+<pre>fname = re.sub(" ", "_", fname)</pre>
+</p>
+
+<p>Regularni izrazi so za tako preprosto zamenjavo prevelik kanon. <code>sub</code> namreč zmore še veliko več; namesto niza, s katerim naj zamenja ujemajoči se podniz, ji lahko podamo kar funkcijo, ki naj jo pokliče ob vsakem ujemanju: funkcija bo kot argument dobila <code>Match object</code>, kot rezultat mora vrniti niz, ki naj zamenja ujemajoči se podniz. Podniz lahko zamenjamo z, recimo, enakim podnizom, vendar zapisanim z velikimi črkami.</p>
+
+<pre>>>> def velike_crke(mo):
+... return mo.group().upper()
+...
+>>> print re.sub(r"[A-Z]\w+", velike_crke, msg)
+DRAGI AHMED,
+
+kako si kaj? UPAM, da so otroci ze zdravi.
+
+MUSTAFA, OSAMA in jaz smo se sli danes malo razgledat in
+kaze kar dobro. ABDULAH pa ni mogel zraven, je sel v PESAVAR
+prodat se tri kamele. OSAMA sicer pravi, da se mu to pred
+zimo ne splaca, ampak saj ves, kaksen je ABDULAH. HARUN in
+on, nic jima ne dopoves, se OSAMA ne. JIBRIL me klice,
+moram iti; oglasi se kaj na SKYPE!
+
+tvoj HUSEIN
+
+</pre>>
+
+<p>Da ne definiramo brez potrebe takšnih kratkih funkcij, si lahko pomagamo z lambda-funkcijami. O njih se pri Programiranju 1 nismo pogovarjali; primeren trenutek je bil kvečjemu prejšnji teden, saj je njihovo naravno mesto funkcijsko programiranje. Tule jih bomo samo uporabili, v izziv radovednim:
+<pre>print(re_ime.sub(lambda mo: mo.group().upper(), msg))</pre>
+Za primer obrnimo vsa imena po arabsko, z desne na levo:
+
+<pre>>>> print(re.sub(r"[A-Z]\w+", lambda mo: "".join(reversed(mo.group())), msg))
+...
+igarD demhA,
+
+kako si kaj? mapU, da so otroci ze zdravi.
+
+afatsuM, amasO in jaz smo se sli danes malo razgledat in
+kaze kar dobro. haludbA pa ni mogel zraven, je sel v ravaseP
+prodat se tri kamele. amasO sicer pravi, da se mu to pred
+zimo ne splaca, ampak saj ves, kaksen je haludbA. nuraH in
+on, nic jima ne dopoves, se amasO ne. lirbiJ me klice,
+moram iti; oglasi se kaj na epykS!
+
+tvoj niesuH
+</pre></p>
+
+<p>Še ena (in zadnja, za danes) zanimiva funkcija, ki jo nudijo regularni izrazi, je <code>split</code>. Tej smo doslej podajali točen podniz, po katerem naj deli. Tako bo tudi poslej, modul za regularne izraze pa ima še eno, drugo funkcijo <code>split</code>, ki ji povemo vzorec, po katerem naj deli. Lahko, recimo, rečemo, naj sporočilo razdeli ob vsakem imenu (kar ni posebej uporaben zgled, je pa preprost).</p>
+
+<pre>
+>>> re.split(r"[A-Z]\w+", msg)<br/>
+['', ' ', ',\n\nkako si kaj? ', ', da so otroci ze zdravi.\n\n', ', ',
+' in jaz smo se sli danes malo razgledat in\nkaze kar dobro. ',
+' pa ni mogel zraven, je sel v ', '\nprodat se tri kamele. ',
+' sicer pravi, da se mu to pred\nzimo ne splaca, ampak saj ves, kaksen je ',
+'. ', ' in\non, nic jima ne dopoves, se ', ' ne. ',
+' me klice,\nmoram iti; oglasi se kaj na ', '!\n\ntvoj ', '\n\n']
+</pre>
+
+<p>V zvezi z <code>Match objecti</code> so nam ostale še skupine. Kaj so in kaj počnemo z njimi, pa bomo videli na primerih.</p>
+
+<h2>Prevedeni regularni izrazi</h2>
+
+<p>V davnih časih smo se pri predmetih, kot je Uvod v računalništvo, učili Moorevoih in Mealyjevih avtomatov. Danes so menda iz mode, zato bo moral ta, ki ga zanima, kako delujejo regularni izrazi, najbrž počakati do Prevajalnikov in navideznih strojev, ki si jih lahko izbere v tretjem letniku. Za zdaj vam lahko povem le, da se izraz, preden lahko računalnik v resnici naredi karkoli z njim, prevede v nekaj, čemur se reče <em>končni avtomat</em>, ta pa je neke vrste usmerjen graf. (Tako da boste vsaj vedeli, čemu služi to, kar počnete pri Diskretnih strukturah.)</p>
+
+<p>Za razumevanje tega, kar je pomembno tu, je bistveno le: regularni izraz se mora v nekaj <em>prevesti</em>. Če napišete program, ki vsebuje nekaj takšnega:
+<pre>for i in range(10000):
+ re.findall(r"[A-Z]\w+", msg[i])
+ # in potem še kaj naprej</pre>
+bo moral nesrečni računalnik deset tisočkrat prevesti regularni izraz <code>[A-Z]\w+</code> v tisto, v kar ga pač prevaja. Pri tako kratkem in preprostem izrazu še gre, pri čem bolj zapletenem pa bi program tekel bistveno hitreje, če bi ga lahko prevedli enkrat za vselej. In res ga lahko.</p>
+
+<p><pre>re_ime = re.compile(r"[A-Z]\w+")
+for i in range(10000):
+ re_ime.findall(msg[i])
+ # in potem še kaj naprej</pre>
+Spremenljivka <code>re_ime</code> (spet madžarska notacija!) zdaj predstavlja prevedeni izraz. Objekt <code>re_ime</code> ima vse funkcije, ki smo jih poznali prej, le da gre zdaj pač za metode objekta in ne funkcije modula: namesto <code>re.findall</code>, <code>re.finditer</code>, <code>re.sub</code>, <code>re.split</code>, zdaj pišemo <code>re_ime.findall</code>, <code>re_ime.finditer</code>, <code>re_ime.sub</code>, <code>re_ime.split</code>. Poleg tega pa še izpustimo prvi argument, vzorec, saj je izraz že pripravljen.</p>
+
+<p>Sam vedno eksplicitno pokličem prevajanje izraza, preden ga uporabim; tako sem se navadil in škoditi ne more. Tule pa bomo počeli, kakor bo naneslo.</p>
+
+<h2>Primeri</h2>
+
+<h3>Primer: teroristi</h3>
+
+<p>Rešimo, za začetek, kar staro nalogo, ki je zahtevala, da izpišemo, kolikokrat se v sporočilu pojavi posamezno ime.</p>
+
+<pre>import re
+
+imena = {}
+for ime in re.findall(r"[A-Z]\w+", msg):
+ imena.setdefault(ime, 0)
+ imena[ime] += 1
+
+for ime, stevec in imena.items():
+ print("{} {}".format(ime, stevec))</pre>
+
+<p>Gre pa, jasno, tudi hitreje: modul <code>collections</code> ima razred <code>Counter</code>, ki prešteje število ponovitev v poslanem seznamu, slovarju, množici... Takole:
+
+<pre>>>> collections.Counter(re.findall(r"[A-Z]\w+", msg))
+Counter({'Osama': 3, 'Abdulah': 2, 'PS': 1, 'Ahmed': 1, 'Mustafa': 1, 'Python': 1,
+'Pesavar': 1, 'Upam': 1, 'Dragi': 1, 'Harun': 1, 'Husein': 1, 'Skype': 1,
+'Jibril': 1})</pre>
+
+<p>To naš program precej poenostavi, saj nas odreši glavnega opravila, štetja.</p>
+
+
+<pre>import re, collections
+for ime, stevec in collections.Counter(re.findall(r"[A-Z]\w+", msg)).items():
+ print("{} {}".format(ime, stevec))</pre>
+
+
+<h3>Primer: branje vseh slik z določene strani</h3>
+
+<p>Tisti, ki vsaj malo poznate HTML, veste, da so slike na spletnih straneh praviloma postavljene tako, da HTML vsebuje značko IMG, ki videti nekako tako:
+<xmp><img src="ime_slike.gif"/></xmp>
+Z malo potencialnimi komplikacijami, seveda. Oklepaju sicer vedno sledi najprej <code>img</code>, a poleg <code>src</code> lahko naletimo še na kaj drugega. Lepo vzgojen HTML ima poleg <code>src</code> vsaj še <code>alt</code>, slabo vzgojen pa tudi <code>onclick</code> ali kaj hujšega. Poleg tega so lahko kjerkoli, razen sredi besed, tudi presledki.</p>
+
+<p>Kar iščemo, lahko z besedami opišemo kot oklepaj (namreč tale: <code><</code>), ki mu lahko sledi kaj praznega prostora, nato pa <code>img</code>. Potem sme biti čisto karkoli razen zaklepaja, <code>></code> in nekoč mora priti na vrsto <code>src</code>, pred njim pa mora biti kak znak, ki ni črka (če bi, recimo, pisalo <code>mesrc</code>, to ni OK, če je pred <code>src</code> presledek, tabulator, nova vrsta ali, zaradi mene tudi narekovaj ali vejica (čeprav to ni povsem po predpisih) pa je OK. Besedi <code>src</code> mora slediti enačaj, pred in za njim sme biti bel prostor. Nato pa narekovaj, za katerim pride tisto, kar nas v resnici zanima: URL slike. Ta je sestavljen iz poljubnih znakov razen narekovajev... in na koncu pride še narekovaj.</p>
+
+<p>Z regularnim izrazom taisto povemo tako:
+<pre><\s*img[^>]*\Wsrc\s*=\s*"([^"]*)"</pre>
+Se pravi
+<table>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code><</code></td><td>oklepaj, <</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td>poljubno praznega prostora (lahko tudi nič)</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>img</code></td><td>beseda <code>img</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>[^>]*</code></td><td>poljubno dolga poljubna šara, razen zaključnega oklepaja</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\W</code></td><td>karkoli, kar ni črka</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>src</code></td><td>beseda <code>src</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td>poljubno praznega prostora (lahko tudi nič)</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>=</code></td><td>enačaj</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td>poljubno praznega prostora (lahko tudi nič)</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>"</code></td><td>narekovaj</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>([^"]*)</code></td><td>poljubne reči razen narekovaja; čemu oklepaji, bomo še videli</td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>"</code></td><td>narekovaj</td></tr>
+</table>
+
+Ko smo rekli <em>poljubno dolga poljubna šara, razen zaključnega zaklepaja</em>: kdo nam jamči, da ta "poljubna šara" ne bo požrla tudi <code>src</code>ja in URLja? To nam jamči sam regularni izraz! Če bi <code>[^>]*</code> požrl <code>src</code>, potem se regularni izraz ne bi mogel ujemati s sicer pravilno vstavljeno sliko. Regularni izraz (oziroma knjižnica, ki ga poganja) pa vedno naredi vse, da bi se izraz ujemal, torej tudi ukroti požrešne dele.</p>
+
+<p>Podobno, a ravno obratno vprašanje je, kdo zagotavlja, da bo <code>([^"]*)</code>, to so, <em>poljubne reči razen narekovaja</em>, res požrle ves URL in ne odnehale, tako, za hec, po treh znakih? Tako kot prej, nam tudi to zagotavlja preostanek izraza. V opisu regularnega izraza delu <code>([^"]*)</code> namreč sledi <code>"</code>; če <code>([^"]*)</code> ne požre vsega do narekovaja, se tisto, kar sledi, ne bo ujemalo z narekovajem, kot zahteva izraz.</p>
+
+<p>Je kdo opazil, da nikjer ne lovimo zaključnega zaklepaja, <code>></code>. Res je, ne omenjamo ga. Z razlogom. Vseeno nam je zanj. Ko ulovimo URL, nam je za vse, kar se dogaja naprej, vseeno. Da bo še jasneje, pripnimo h gornji tabeli še en preprost in en zapleten podniz, ki se ujame v izraz. Najprej<code><img src="ime_slike.gif"/></code></p>
+
+<table>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code><</code></td><td><code><</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>img</code></td><td><code>img</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>[^>]*</code></td><td></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\W</code></td><td><em>(presledek med <code>img</code> in <code>src</code>)</em></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>src</code></td><td><code>src</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>=</code></td><td><code>=</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>"</code></td><td><code>"</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>([^"]*)</code></td><td><code>ime_slike.gif</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>"</code></td><td><code>"</code></td></tr>
+</table>
+
+<p>Pa še bolj zapleten primer: kako regularni izraz zgrabi <code>< img alt="Tvoj brskalnik ne kaže slik" onclick="http://www.fri.uni-lj.si" src= "x.gif" class="psl" ></code></p>
+
+<table>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code><</code></td><td><code><</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td><em>(presledka pred <code>img</code>)</em></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>img</code></td><td><code>img</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>[^>]*</code></td><td><code> alt="Tvoj brskalnik ne kaže slik" onclick="http://www.fri.uni-lj.si" </code> <em>(pred <code>src</code> so trije presledki; "šara" pograbi prva dva)</em></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\W</code></td><td><em>(tretji presledek pred <code>src</code>)</em></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>src</code></td><td><code>src</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>=</code></td><td><code>=</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>\s*</code></td><td><em>(presledek)</em></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>"</code></td><td><code>"</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>([^"]*)</code></td><td><code>x.gif</code></td></tr>
+<tr><td style="background-color: #cccccc; text-align: right; padding: 2px"><code>"</code></td><td><code>"</code></td></tr>
+</table>
+
+<p>Zdaj pa uporabimo izraz v funkciji <code>download_images</code>, ki bo kot argument sprejela URL strani in ciljni direktorij. Prebrala bo s stran, nato pa z nje snela vse slike in jih shranila v ciljni direktorij.</p>
+
+<pre>import re
+import urllib.request
+import os
+
+re_img = re.compile(r'<\s*img[^>]*\Wsrc\s*=\s*"([^"]*)"', re.DOTALL + re.IGNORECASE)
+
+def download_images(url, target_dir):
+ if not os.path.exists(target_dir):
+ os.mkdir(target_dir)
+ elif not os.path.isdir(target_dir):
+ raise IOError("'%s' is not a directory" % target_dir)
+
+ page = urllib.request.urlopen(url).read().decode("utf-8")
+ for mo in re_img.finditer(page):
+ img_url = mo.group(1)
+ img = urllib.request.urlopen(img_url).read()
+ img_name = os.path.split(img_url)[1]
+ target_name = os.path.join(target_dir, img_name)
+ open(target_name, "wb").write(img)</pre>
+
+
+<p>Regularni izraz smo tokrat enkrat za vselej prevedli pred funkcijo in ga shranili v <code>re_img</code>. Dodali smo še en argument, zastavice, s katerimi povemo, naj vzorec <code>.</code> požira tudi znake za novo vrstico, <code>\n</code> (<code>re.DOTALL</code>) in da nam je vseeno za male in velike črke (<code>re.IGNORECASE</code>), tako da se bo, denimo, <code>img</code> ujemal tudi z <code>IMG</code>, <code>Img</code> in <code>iMg</code>...
+
+<p>Funkcija nato preveri ali ciljni direktorij obstaja; če ga ni, ga naredi, če je, pa preveri, ali je v resnici direktorij in v nasprotnem primeru - kot znamo od prejšnjič - sproži izjemo.</p>
+
+<p>Sledi pravo delo: poberemo podano stran. Kar dobimo z <code>read</code>, ni niz (<code>str</code>) temveč bajti (<code>bytes</code>). Ker regularni izrazi brskajo le po nizih, pretvorimo bajte v niz. Če bi hoteli narediti, kot se šika, bi ugotovili, kako je stran v resnici kodirana, tule pa bomo predpostavili, da gre za utf-8 in poklicali <code>decode("utf-8")</code>
+
+Potem poiščemo vse pojavitve značke <code>img</code>. Zdaj pride, kar sem zamolčal: <code>group</code>. Ko smo v prejšnjih primerih želeli iz <code>match object</code>a dobiti podniz, ki se je prilegal regularnemu izrazu, smo poklicali <code>group()</code>. Zdaj bi želeli priti do podniza, ki se prilega URLju slike, torej tistemu med narekovaji za <code>src</code>em. Zato smo ta del zaprli v oklepaje in s tem naredili novo skupino. Do njene vsebine pridemo z <code>group(1)</code>. Čemu <code>1</code>? Zato pač, ker gre za prvo skupino. Če bi jih imeli več, bi jih lepo prešteli in povedali, ali želimo vsebino tretje ali šeste.</p>
+
+<p>"Lepo prešteli"? Tole štetje ni prav nič lepo in prikladno. Regularni izrazi so zapleteni in kaj lahko se uštejemo. Še huje: ko izraz spremenimo, bomo morda dodali novo skupino in vse bo treba preštevilčiti. Zato je skupine bolj praktično poimenovati. Namesto
+<pre>re_img = re.compile(r'<\s*img[^>]*\Wsrc\s*=\s*"([^"]*)"', re.DOTALL + re.IGNORECASE)</pre>
+napišemo
+<pre>re_img = re.compile(r'<\s*img[^>]*\Wsrc\s*=\s*"(?P<url>[^"]*)"', re.DOTALL + re.IGNORECASE)</pre>
+in skupina je dobila ime, <code>url</code>. Do vsebine te skupine potem pridemo z <code>mo.group("url").</code></p>
+
+<p>Sliko poberemo v niz z <code>urllib.request.urlopen</code>. Zdaj pa jo je potrebno še shraniti. URL slike bo, recimo, takšen: <code>http://i1.nyt.com/images/2011/01/01/arts/design/01moth_costa/01moth_costa-moth.jpg</code> in takšno sliko želimo shraniti pod imenom <code>01moth_costa-moth.jpg</code>. Da pridemo do njega, lahko za prvi približek posilimo kar funkcijo <code>os.path.split(p)</code>, ki kot argument (običajno) prejme datoteko skupaj s potjo do nje, na primer, <code>c:\Users\Janez\Desktop\desktop.ini</code>, kot rezultat pa vrne terko s potjo in datoteko, na primer <code>("c:\Users\Janez\Desktop", "desktop.ini")</code>. URL je dovolj podobna reč, da ga bo <code>os.path.split</code> pravilno razcepil.</p>
+
+<p>Funkcija deluje, lahko jo preskusimo. Ker vaje ni nikoli preveč, pa poskusimo spremeniti izraz tako, da se izognemo klicu <code>os.path.join</code>. Pot vodi prek razmisleka, da je URL sestavljen iz dveh delov; prvi del je pot, drugi ime datoteke in slednji ne vsebuje poševnic, <code>/</code>. (Tole je le zmerno res: pri, denimo, slikah, ki se generirajo sproti, URL navadno ne bo vseboval imena datoteke. Ta detajl za zdaj pozabimo.) Ime, torej tisto med narekovaji, bi zato spremenili iz <code>[^"]*</code>, karkoli razen narekovaja, v <code>[^"]*[^"/]*</code>, to je, karkoli razen narekovaja, ki mu sledi karkoli razen narekovaja in poševnice. No, tole še ne bo povsem dobro, saj prvemu delu nič ne preprečuje, da ne bi kar vedno požrl celotnega imena in za drugi del ne bi ostalo nič. Prva rešitev, ki nam pride na misel, je <code>[^"]*/[^"/]*</code>, torej, karkoli razen narekovaja, ki mu sledi poševnica in potem karkoli razen narekovaja in poševnice. Tudi to ne bo povsem dobro, saj zdaj zahtevamo, da URL vsebuje poševnico in zdaj ne bomo več prepoznali povsem legalne slike <code><img src="ime_slike.gif"/></code>. Prava rešitev je, da prvemu delu pač zapovemo, naj požre čim manj, tako da zvezdici dodamo vprašaj, <code>[^"]*?[^"/]*</code>. Zdaj vse deluje, kot mora: prvi del lahko požira karkoli (vendar čim manj), drugi pa ne mara poševnic. Če obstaja (vsaj ena) poševnica, bo prvi del požrl vse do (zadnje) poševnice in tako omogočil drugemu, da se ujame (a požrl ne bo nič več, kot je nujno treba, saj požira čim manj). Če pa poševnice sploh ni, bo vse požrl drugi del, saj prvemu ni potrebno požreti ničesar.</p>
+
+<p>In zdaj določimo in poimenujmo še skupine. Dve bosta in ena bo del druge. Najprej poskrbimo za ime datoteke, se pravi to, kar sledi poševnici: <code>[^"]*?(?P<fname>[^"/]*)</code>. Vse skupaj pa zapremo še enkrat, saj celoten URL vsebuje tako prvi del kot tudi ime: <code>(?P<url>[^"]*?(?P<fname>[^"/]*))</code>. Funkcija s takšnim regularnim izrazom je taka:</p>
+
+<pre>re_img = re.compile(r'<\s*img[^>]*\Wsrc\s*=\s*"(?P<url>[^"]*?(?P<fname>[^"/]*))"', re.DOTALL+re.IGNORECASE)
+
+def download_images(url, target_dir):
+ if not os.path.exists(target_dir):
+ os.mkdir(target_dir)
+ elif not os.path.isdir(target_dir):
+ raise IOError("{}' is not a directory".format(target_dir))
+ page = urllib.request.urlopen(url).read().decode("utf-8")
+ for mo in re_img.finditer(page):
+ img_url, img_name = mo.group("url", "fname")
+ img = urllib.request.urlopen(img_url).read()
+ target_name = os.path.join(target_dir, img_name)
+ open(target_name, "wb").write(img) </pre>
+
+</body>
+</html>
diff --git a/python/problems/re/sl.py b/python/problems/re/sl.py new file mode 100644 index 0000000..e450d4c --- /dev/null +++ b/python/problems/re/sl.py @@ -0,0 +1,2 @@ +name = 'Regularni izrazi' +description = 'Vaje iz <a href="[%@resource re_sl.html%]">regularnih izrazov</a>' diff --git a/python/problems/re/words/common.py b/python/problems/re/words/common.py new file mode 100644 index 0000000..79a94db --- /dev/null +++ b/python/problems/re/words/common.py @@ -0,0 +1,69 @@ +import re +from python.util import has_token_sequence, string_almost_equal, \ + string_contains_number, get_tokens, get_numbers, get_exception_desc, \ + all_tokens, has_comprehension, has_loop, almost_equal, get_ast +from server.hints import Hint + +id = 20905 +number = 5 +visible = True + +solution = '''\ +import re + +def words(s): + return re.findall(r'[a-zA-Z]+', s) +''' + +hint_type = { + 'final_hint': Hint('final_hint') +} + +def test(python, code, aux_code=''): + func_name = 'words' + tokens = get_tokens(code) + if not has_token_sequence(tokens, ['def', func_name]): + return False, [{'id' : 'no_func_name', 'args' : {'func_name' : func_name}}] + + in_out = [ + ('Vse besede brez locil', ['Vse', 'besede', 'brez', 'locil']), + ('Vse besede. Brez locil!!!', ['Vse', 'besede', 'Brez', 'locil']), + ('', []), + ('Ulica st. 15a', ['Ulica', 'st', 'a']), + ('Visina 180.42, teza 76.5', ['Visina', 'teza']), + ] + + test_in = [('{0}("{1}")'.format(func_name, str(l[0])), None) + for l in in_out] + test_out = [l[1] for l in in_out] + + answers = python(code=aux_code+code, inputs=test_in, timeout=1.0) + n_correct = 0 + tin, tout = None, None + for i, (ans, to) in enumerate(zip(answers, test_out)): + res = ans[0] + corr = res == to + n_correct += corr + if not corr: + tin = test_in[i][0] + tout = to + + passed = n_correct == len(test_in) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_in)}}] + if tin: + hints.append({'id': 'problematic_test_case', 'args': {'testin': str(tin), 'testout': str(tout)}}) + if passed: + hints.append({'id': 'final_hint'}) + + return passed, hints + + +def hint(python, code, aux_code=''): + tokens = get_tokens(code) + + # run one test first to see if there are any exceptions + answer = python(code=aux_code+code, inputs=[(None, None)], timeout=1.0) + exc = get_exception_desc(answer[0][3]) + if exc: return exc + + return None diff --git a/python/problems/re/words/en.py b/python/problems/re/words/en.py new file mode 100644 index 0000000..3392baf --- /dev/null +++ b/python/problems/re/words/en.py @@ -0,0 +1,13 @@ +id = 20905 +name = 'Words' + +description = '''\ +<p>(translation missing)</p>''' + +hint = { + 'plan': '''\ +<p>(translation missing)</p>''', + + 'no_input_call': '''\ +<p>(translation missing)</p>''', +} diff --git a/python/problems/re/words/sl.py b/python/problems/re/words/sl.py new file mode 100644 index 0000000..ad5fe4c --- /dev/null +++ b/python/problems/re/words/sl.py @@ -0,0 +1,24 @@ +import server +mod = server.problems.load_language('python', 'sl') + +id = 20905 +name = 'Besede' + +description = '''\ +<p> +Napišite funkcijo <code>words(s)</code>, ki vrne vse besede v nizu. Beseda +je sestavljena iz malih in velikih črk. Pazite, da ne vračate ločil. +<pre> +>>> words('Vse besede. Brez locil!!!') +['Vse', 'besede', 'Brez', 'locil'] +</pre> +''' + +plan = [] + +hint = { + 'final_hint': ['''\ +<p>Program je pravilen! <br> +</p> +'''], +} diff --git a/python/problems/recursion/recursion_sl.html b/python/problems/recursion/recursion_sl.html new file mode 100644 index 0000000..340055e --- /dev/null +++ b/python/problems/recursion/recursion_sl.html @@ -0,0 +1,407 @@ +<!DOCTYPE html>
+<html lang="sl">
+<head>
+<meta charset="utf-8" />
+<title></title>
+<link rel="stylesheet" type="text/css" href="/css/codeq.css" />
+<link rel="stylesheet" type="text/css" href="../../style.css" />
+</head>
+<body>
+
+<h1>Rekurzija na primeru družinskega drevesa </h1>
+
+<p>Najstarejši član rodbine Novakovih, Adam, 111 let, ima štiri otroke: Matjaža,
+Cilko, Danieka in Erika. Matjaž ima enega, namreč Viljema. Danijel ima
+Elizabeto in Hansa (kasneje se je namreč preselil v predmestje Graza, kjer ima
+manjše podjetje), Cilka in Erik pa nimata otrok. In tako naprej. Vse skupaj je
+nabrano v spodnjem slovarju.</p>
+<pre>
+otroci = {
+ "Adam": ["Matjaž", "Cilka", "Daniel", "Erik"],
+ "Aleksander": [],
+ "Alenka": [],
+ "Barbara": [],
+ "Cilka": [],
+ "Daniel": ["Elizabeta", "Hans"],
+ "Erik": [],
+ "Elizabeta": ["Ludvik", "Jurij", "Barbara", "Herman", "Mihael"],
+ "Franc": [],
+ "Herman": ["Margareta"],
+ "Hans": [],
+ "Jožef": ["Alenka", "Aleksander", "Petra"],
+ "Jurij": ["Franc", "Jožef"],
+ "Ludvik": [],
+ "Margareta": [],
+ "Matjaž": ["Viljem"],
+ "Mihael": [],
+ "Petra": [],
+ "Tadeja": [],
+ "Viljem": ["Tadeja"],
+}
+</pre>
+
+<p>Znane so tudi starosti vseh teh ljudi.</p>
+<pre>
+starost = {
+ "Adam": 111, "Matjaž": 90, "Cilka": 88, "Daniel": 85, "Erik": 83,
+ "Viljem": 58, "Tadeja": 20, "Elizabeta": 68, "Hans": 64, "Ludvik": 50,
+ "Jurij": 49, "Barbara": 45, "Herman": 39, "Mihael": 32, "Franc": 30,
+ "Jožef": 29, "Margareta": 3, "Alenka": 9, "Aleksander": 5, "Petra": 7}
+</pre>
+
+<p>Napišimo, takole, za preprosto vajo, funkcijo, ki ji podamo osebo in pove,
+koliko otrok ima.</p>
+<pre>
+def stevilo_otrok(oseba):
+ return len(otroci[oseba])
+</pre>
+
+<p>Če pokličemo, recimo, `stevilo_otrok("Adam")`, dobimo 4.</p>
+
+<p>Kako pa bi izvemo število vnukov posamezne osebe? Tako da gremo prek vseh
+ otrok in seštevamo število njihovih otrok, recimo z</p>
+<pre>
+def stevilo_vnukov(oseba):
+ v = 0
+ for otrok in otroci[oseba]:
+ v += len(otroci[otrok])
+ return v
+</pre>
+
+<p>ali, brez velike razlike,</p>
+<pre>
+def stevilo_vnukov(oseba):
+ v = 0
+ for otrok in otroci[oseba]:
+ v += stevilo_otrok(otrok)
+ return v
+</pre>
+
+<p>Uh, kaj kompliciramo, saj že znamo</p>
+<pre>
+def stevilo_vnukov(oseba):
+ return sum(stevilo_otrok(otrok) for otrok in otroci[oseba])
+</pre>
+
+<h2> Velikost rodbine </h2>
+
+<p>Do sem - nič posebnega. Zdaj pa pridejo zanimive reči: za nekoga nas zanima,
+velika je njegova rodbina, skupaj z njim, njegovimi otroki, vnuki, pravnuki
+in tako naprej. Recimo, da ima rojstni dan in bo povabil vso svojo rodbino
+na večerjo. Koliko krožnikov za juho potrebuje. </p>
+
+<p> Kaj mu je storiti? Vse svoje otroke, bo vprašal, kako velike so njihove
+rodbine. To bo seštel in prištel še sebe. Kako bodo ti otroci izvedeli velikosti
+svojih rodbin? Tako, da bodo vprašali vse svoje otroke po velikosti njihovih
+rodbin, to sešteli in prišteli še sebe. Pa njihovi otroci? Enako. </p>
+
+<p>Spremenimo to v funkcijo. Velikost rodbine dobimo tako, da gremo prek otrok
+in seštevamo velikosti njihovih rodbin.</p>
+<pre>
+def velikost_rodbine(oseba):
+ v = 0
+ for otrok in otroci[oseba]:
+ v += velikost_rodbine(otrok)
+ return v + 1
+</pre>
+
+<p>Za tiste, ki so se dobro naučili prejšnjo snov:</p>
+<pre>
+def velikost_rodbine(oseba):
+ return sum(velikost_rodbine(otrok) for otrok in otroci[oseba]) + 1
+</pre>
+
+<h2> Poišči osebo </h2>
+
+<p>Potem nas je zanimalo, kako odkriti, ali je v rodbini določene osebe oseba
+s takšnim in takšnim imenom. Storiti nam je tole: če je tako ime ravno vprašani
+osebi, bo odgovorila <code>True</code>. Sicer bo enega za drugim spraševala
+otroke, dokler prvi ne odgovori <code>True</code>; tedaj vrnemo
+<code>True</code>. Če noben otrok nima takšnega potomca - in torej noben otrok
+ne odgovori <code>True</code>, odgovorimo <code>False</code>. Z drugimi
+ besedami,</p>
+<pre>
+def obstaja_ime(oseba, ime):
+ if oseba == ime:
+ return True
+ for otrok in otroci[oseba]:
+ if obstaja_ime(otrok, ime):
+ return True
+ return False
+</pre>
+
+<p>S snovjo izpred dveh tednov:</p>
+<pre>
+def obstaja_ime(oseba, ime):
+ return oseba == ime or any(obstaja_ime(otrok, ime) for otrok in otroci[oseba])
+</pre>
+
+<h2> Največja družina </h2>
+
+<p>Kako velika je največja družina v rodbini neke osebe - s temi mislimo le
+otroke, brez staršev? Tu osebe razmišljajo tako: najprej predpostavijo, da je to
+njihova družina - največ otrok je torej toliko otrok, kolikor jih imajo oni.
+Potem vprašajo vsakega od otrok, kako velika je največja družina v njegovi
+rodbini. Če naleti na koga z večjo družino, si to zapomni. Na koncu vrne največ,
+kar je videl.</p>
+<pre>
+def najvec_otrok(oseba):
+ najvec = len(otroci[oseba])
+ for otrok in otroci[oseba]:
+ koliko = najvec_otrok(otrok)
+ if koliko > najvec:
+ najvec = koliko
+ return najvec
+</pre>
+
+<p>Kdor zna, zna takole:</p>
+<pre>
+def najvec_otrok(oseba):
+ return max([len(otroci[oseba])] + [najvec_otrok(otrok) for otrok in otroci[oseba]])
+</pre>
+
+
+<p>Študenti pogosto zagrešijo tole precej napačno rešitev:</p>
+<pre>
+def najvec_otrok(oseba):
+ najvec = len(otroci[oseba])
+ for otrok in otroci[oseba]:
+ if najvec_otrok(otrok) > najvec:
+ najvec = najvec_otrok(otrok)
+ return najvec
+</pre>
+
+<p>Tu vsaka oseba večkrat vpraša svoje otroke, kako velike so največje družine.
+Vsaj tistega z največjo družino vpraša dvakrat. Doslej smo takšno programiranje
+tolerirali (in le občasno postokali, da to ni najlepše). Tu ga ne moremo več.
+Vsak od teh, ki so bili po nepotrebnem vprašani dvakrat, spet dvakrat vpraša
+svoje otroke vpraša po dvakrat, torej se v bistvu vprašajo po štirikrat. Oni
+spodaj so vprašani že po osemkrat. Pri tako majhnih drevesih to ni tako hudo.
+Pri velikih pa si tega ne bi mogli privoščiti.</p>
+
+
+<h2> Najdaljše ime v rodbini </h2>
+
+<p>Katero je najdaljše ime v rodbini neke osebe? Tole je precej podobno
+največjemu številu otrok: najdaljše je moje, razen če je daljše katero od imen
+v rodbini katerega od otrok.</p>
+<pre>
+def najdaljse_ime(oseba):
+ najdaljse = oseba
+ for otrok in otroci[oseba]:
+ naj_pod = najdaljse_ime(otrok)
+ if len(naj_pod) > len(najdaljse):
+ najdaljse = naj_pod
+ return najdaljse
+</pre>
+
+<h2> Globina rodbine </h2>
+
+<p>Kako globoka je rodbina določene osebe? Torej, nekdo, ki nima otrok, ima
+rodbino globinee 1. Če ima otroke, nima pa vnukov, ima rodbino globine
+2. Če ima tudi kakega vnuka, vendar nobenega pravnuka, ima rodbino globine 3.</p>
+
+<p>Globino rodbine izračunamo tako, da vprašamo vse otroke po globinah njihovih
+rodbin in k največji globini, ki jo dobimo, prištejemo 1.</p>
+<pre>
+def globina(oseba):
+ najvecja = 0
+ for otrok in otroci[oseba]:
+ g = globina(otrok)
+ if g > najvecja:
+ najvecja = g
+ return najvecja + 1
+</pre>
+
+<p>Ali, krajše</p>
+<pre>
+def globina(oseba):
+ return max(globina(otrok) for otrok in otroci[oseba]) + 1
+</pre>
+
+<h2> Velikost potomstva </h2>
+
+<p>Pripadnike Novakove rodbine smo nato spraševali, koliko potomstva imajo. S
+potomci mislimo nekaj takšnega kot rodbino, a brez te osebe same. Jurij
+(ki ima dva otroka in tri vnuke) ima pet potomcev).</p>
+
+<p>Tale zahteva malo razmisleka. Navidez bi jo lahko ugnali tako, kot smo
+velikost rodbine, le 1 ne smemo prišteti na koncu, saj oseba ne sme šteti
+sebe.</p>
+<pre>
+def velikost_rodbine(oseba):
+ v = 0
+ for otrok in otroci[oseba]:
+ v += velikost_rodbine(otrok)
+ return v
+</pre>
+
+<p>Zoprna reč je, da je ta funkcija nekoliko napačna. No, precej napačna.
+Vedno vrne 0 - ker nihče nikoli ničesar ne prišteje, vse seštevajo samo ničle.
+In iz ničel nikoli ne boš dobil ničesar, pa jih seštevaj, kolikor dolgo hočeš. </p>
+
+
+<p>Ena rešitev je, da vsak vrne število svojih otrok, ki čemur morajo otroci
+prišteti število svojih otrok, in vnuki število svojih...</p>
+<pre>
+def stevilo_potomcev(oseba):
+ potomcev = len(otroci[otroci])
+ for otrok in otroci[oseba]:
+ potomcev += stevilo_potomcev(otrok)
+ return potomcev
+</pre>
+
+<p>Ali isto, le na drug način:</p>
+<pre>
+def stevilo_potomcev(oseba):
+ potomcev = 0
+ for otrok in otroci[oseba]:
+ potomcev += 1 + stevilo_potomcev(otrok)
+ return potomcev
+</pre>
+
+<p>Lahko pa si pomagamo tudi z rodbino:</p>
+<pre>
+def stevilo_potomcev(oseba):
+ return velikost_rodbine(oseba) - 1
+</pre>
+
+<h1> Rekurzivne funkcije na seznamih </h1>
+
+<h2>Palindrom</h2>
+
+<p>Niz je palindrom, če se naprej in nazaj bere enako.</p>
+
+<p>Pisati funkcijo, ki pove, ali je podani niz palindrom, je sicer železni
+repertoar pouka programiranja - v Pythonu pa je nekoliko trivialen. Če imamo
+niz <code>s</code>, bomo z <code>s[1:-1]</code> dobili obrnjen niz, niz
+"prebran" nazaj. Če sta tadva enaka, je funkcija palindrom.</p>
+
+<p>Ker vem, da ste pametni ljudje, ne boste pisali</p>
+
+<pre>def palindrom(s):
+ if s == s[::-1]:
+ return True
+ else:
+ return False</pre>
+
+<p>temveč, kratko in jedrnato</p>
+
+<pre>def palindrom(s):
+ return s == s[::-1]</pre>
+
+<p>Zdaj pa recimo, da za tole obračanje nizov ne vemo (ali pa, da programiramo
+v jeziku, ki česa takšnega nima. V tem primeru potrebujemo zanko: rekli bomo,
+da je <code>s</code> palindrom, če je ničti znak enak zadnjemu, prvi
+predzadnjemu in tako naprej do konca. Po vzorcu, ki nam je že čisto domač,
+bomo ob prvem neujemanju zatulili <code>Falsch!</code>; če pridemo do konca
+brez težav, vrnemo <code>True</code>.</p>
+
+<pre>def palindrom(s):
+ for i in range(len(s)):
+ if s[i] != s[-i - 1]:
+ return False
+ return True</pre>
+
+<p>(Pravzaprav ni potrebno, da gremo "tako naprej do konca", saj bi zadoščalo
+"tako naprej do sredine". A to tule ni pomembno.)</p>
+
+<p>Palindrome lahko definiramo na drug način, ki ne zahteva štetja: rečemo
+lahko, da je <code>s</code> palindrom, če je ničti znak enak zadnjemu in je
+palindrom tudi vse med njima. Poskrbeti je le potrebno za nize, ki nimajo
+dveh znakov: če je niz krajši od dveh znakov, bomo rekli, da je palindrom.</p>
+
+<p>Z enim stavkom: <code>s</code> je palindrom, če je krajši od dveh znakov ALI
+pa je ničti znak enak zadnjemu IN so vsi znaki od prvega do predzadnjega
+palindrom.</p>
+
+<pre>def palindrom(s):
+ return len(s) < 2 or s[0] == s[-1] and palindrom(s[1:-1])</pre>
+
+<p>Študente tole pogosto zbega. Funkcija <code>palindrom</code> je definirana
+s funkcijo <code>palindrom</code>. Sama s sabo. Je to sploh dovoljeno? Deluje?
+</p>
+
+<p>Razlog, da deluje, je v tem, da je niz vedno krajši. Ko se vprašamo, ali je
+"abcdcba" palindrom, funkcija ugotovi, da to sicer ni manj kot dva znaka,
+vendar je palindrom, če je ničti znak enak zadnjemu (kar je) in če je "bcdcb"
+palindrom. Ko preverja, ali je "bcdcb" palindrom, ugotovi, da je daljši od dveh
+znakov, zato bo palindrom, če je ničti enak zadnjemu (kar je) in če je "cdc"
+palindrom. Ta je daljši od dveh znakov, zato bo palindrom, če je ničti znak
+enak zadnjemu in je "d" palindrom. Niz "d" je krajši od dveh znakov, torej je
+palindrom. Zato je torej tudi "cdc" palindrom. Zato je "bcdcb" palindrom.
+Zato je "abcdcba" palindrom.</p>
+
+<h2>Vsota elementov seznama</h2>
+
+<p>Kako bi izračunali vsoto elementov seznama. Z zanko seveda znamo. Pa
+poskusimo še s trikom, kakršnega smo uporabili zgoraj. Za to potrebujemo
+rekurzivno definicijo vsote seznama - definicijo, ki se nanaša sama nase.</p>
+
+<p>Takole lahko rečemo: vsoto elementov seznama dobimo tako, da k prvemu
+elementu prištejemo vsoto ostalih. Če je seznam prazen, pa je vsota 0.</p>
+
+<pre>def vsota(s):
+ if not s:
+ return 0
+ return s[0] + vsota(s[1:])</pre>
+
+<h2>Ima seznam sama soda števila</h2>
+
+<p>Seznam ima sama soda števila, če je prazen ali pa je prvi element sod in
+ima preostanek sama soda števila.</p>
+
+<pre>def soda(s):
+ return not s or s[0] % 2 == 0 and soda(s[1:])</pre>
+
+<h2>Vsota sodih števil v seznamu</h2>
+
+<p>Napisati želimo funkcijo, ki vrne vsoto vseh sodih elementov v podanem
+seznamu.</p>
+
+<p>Če je seznam prazen, je vsota enaka 0. Sicer pa naredimo tole: če je ničti
+element sod, vrnemo vsoto ničtega elementa in vsote ostalih. Če ni sod, vrnemo
+le vsoto ostalih.</p>
+
+<pre>def vsota_sodih(s):
+ if not s:
+ return 0
+ if s[0] % 2 == 0:
+ return s[0] + vsota_sodih(s[1:])
+ else:
+ return vsota_sodih(s[1:])</pre>
+
+<h2>Prvi sodi element seznama</h2>
+
+<p>Napisali bomo funkcijo, ki vrne prvi sodi element.</p>
+
+<p>Če je ničti element sod, vrnemo tega. Sicer vrnemo prvega sodega med
+ostalimi.</p>
+
+<pre>def prvi_sodi(s):
+ if s[0] % 2 == 0:
+ return s[0]
+ else:
+ return prvi_sodi(s[1:])</pre>
+
+<p>Funkcija ima manjšo težavo, kadar v
+seznamu ni nobenega sodega elementa. Ker prvi ni sod, išče sodega v preostanku.
+Ker prvi iz preostanka ni sod, išče sodega v preostanku. Ker prvi v tem
+preostanku ni sod ... in tako naprej, dokler ne pride do praznega seznama.
+Takrat bomo v prvi vrstici funkcije spraševali po prvem (no, ničtem) elementu,
+<code>s[0]</code> in Python bo javil napako, <code>Index out of range</code>.
+</p>
+
+<p>Recimo, da bo funkcija v takšnem primeru vrnila <code>None</code>.</p>
+
+<pre>def prvi_sodi(s):
+ if not s:
+ return None
+ if s[0] % 2 == 0:
+ return s[0]
+ else:
+ return prvi_sodi(s[1:])</pre>
+
+
+</body>
+</html>
|