Naloga

Osrednja obveščevalna služba se je odločila spremljati elektronsko pošto določenih osebkov, pri čemer jo zanimajo predvsem imena, ki se v tej pošti pojavljajo. Da bi bilo njeno delo enostavnejše, se je odločila, naj kot "ime" za vsak slučaj štejemo vsako besedo, ki se začne z veliko začetnico in nadaljuje z malimi. Radi bi imeli program, ki bi za dano elektronsko sporočilo naštel, vsa imena, ki se pojavijo v njem in kolikokrat se pojavijo.

Nekdo jim je že napisal program, ki vzame sporočilo in ga predela v sporočilo brez ločil. Vaš izdelek naj se začne tako (torej: to, kar sledi zdajle, kar skopirajte na začetek svojega programa):

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 PS, oprosti za sumnike - rashmeD nas je preprical, da zdaj uporabljamo Python. """ import re msg = re.sub("[^\w ]", " ", msg)

Zadnji dve vrstici že odstranita ločila, tako da je niz msg po tem takšen: Dragi Ahmed kako si kaj Upam da so otroci ze zdravi Mustafa Osama in jaz smo se sli... in tako naprej

Vaše delo se začne tu.

Preprosta različica naloge: vaš program naj v slednjem nizu (onem, brez ločil) najde in prešteje imena ter izpiše tole: Dragi 1 Ahmed 1 Upam 1 Mustafa 1 Osama 3 Abdulah 2 Pesavar 1 Osama 3 Abdulah 2 Harun 1 Osama 3 Jibril 1 Skype 1 Husein 1 Python 1

Bolj zapletena različica: program izpiše Dragi 1 Ahmed 1 Upam 1 Mustafa 1 Osama 3 Abdulah 2 Pesavar 1 Harun 1 Jibril 1 Skype 1 Husein 1 Python 1

Razlika je v tem, da so v prvi različici imena, ki se pojavijo večkrat, tudi izpisana večkrat. V drugi smo poskrbeli, da se vsako ime pojavi le enkrat.

Bojni načrt (lahko se ga držite ali pa tudi ne)

  1. Pripravite prazen seznam imena
  2. Z metodo split iz sporočila (msg) naredite seznam besed (to je ekvivalentno temu, kar ste delali za prejšnjo domačo nalogo, vendar smete tokrat uporabiti split!)
  3. Sprehodite se po tem seznamu; za vsako besedo preverite ali je ime; če je, ga dodajte na seznam imena
  4. Sprehodite se po seznamu imena; izpišite vsako ime in kolikokrat se pojavi v seznamu (za kar ste na zadnjih predavanjih prav tako spoznali primerno metodo)

Namig: poglej v zapiske predavanj in se spomni, kaj dela metoda title. Nato razmisli: za katere nize velja, da je s.title()==s (torej, da title z njimi v bistvu ne naredi ničesar)?

Če boste delali, kot je opisano zgoraj, bodo nekatera imena izpisana večkrat, torej to zadošča za prvo različico. Kdor zna boljše, naj naredi boljše. (Nalogo je mogoče rešiti tudi v eni sami vrstici, dolgi 107 znakov. Morda pa tudi manj: frajerji na plano!)

Rešitev

Rešitev najpreprostejše različice naloge je prepreprosta.

seznam = msg.split() for beseda in seznam: if beseda.istitle(): print(beseda, seznam.count(beseda))

Če se želimo izogniti temu, da bi bilo isto ime izpisano večkrat, si zapomnimo, kaj smo izpisali doslej.

izpisano = [] seznam = msg.split() for beseda in seznam: if beseda.istitle(): if not beseda in izpisano: print(beseda, seznam.count(beseda)) izpisano.append(beseda)

Opazke

Veliko nerodnosti opažam v pogojnih stavkih - tako v pogojih kot v tem, kako so stavki zloženi skupaj. Tule sta dva primera.

for beseda in seznam: if beseda.isupper(): continue elif besede[0].isupper(): imena.append(niz) Boljše bi bilo napisati, kar mislimo. Koda znotraj if pravi: "če je beseda sestavljena iz samih velikih črk, me ne zanima; sicer pa me zanima samo, če je prva črka velika". To lepše povemo, če rečemo, da "nas zanimajo besede, ki imajo veliko prvo črko, a niso sestavljene iz sestavljene iz samih velikih črk". Povejmo tako tudi v programu: for beseda in seznam: if besede[0].isupper() and not beseda.isupper(): imena.append(niz)

Drug primer: v eni od rešitev sem videl

if x == y and 2 <= n: print(42) elif x == y and 2 > n: print(43) (Namesto 42 in 43 je program sicer seveda izpisoval nekaj drugega).

Program nekaj izpiše le, če je x==y, torej naj se začne z if x == y:, ki naj pokrije oba printa. Znotraj tega pa izpišemo 42, če je n večji ali enak 2 in 43, če ni. Ker navadno primerjamo n z 2 in ne 2 z n, bo tudi program bolj razumljiv, če je pogoj obrnjen tako.

if x == y: if n >= 2: print(42) else: print(43)

Morda se zdijo moje spremembe komu arbitrarne: v prvem programu sem pobrisal en if, v drugem sem ga dodal. Prejšnji teden sem pel hvalospevo neki kodi, kjer smo se s continue na začetku zanke znebili primera, ki nas ni zanimal, danes to razglašam za grdo. Morda so v resnici arbitrarne in subjektivne, vendar se izkaže, da dva različna, a resna programerja, isto stvar (v Pythonu) pogosto sprogramirata na isti način. Občutek za to, kaj je lep in jasen program, je, kot kaže, vendarle univerzalen, pa čeprav morda subjektiven.

Nekateri nekoliko nerodno uporabljate split(). Če ga pokličemo brez argumentov, bo delil po vsem, čemu rečemo beli prostor; "ločilo" med besedami so presledki, tabulatorji in znaki za prehod v novo vrstico; več zaporednih belih znakov razume kot eno samo ločilo. Če ga pokličete z argumenti, pa to ni več tako: split(" ") bo vsak presledek obravnaval kot ločilo tudi, kadar naleti na več zaporednih presledkov. Med dvema zaporednima presledkama bo torej videl prazno besedo. Tisti, ki so poklicali split na ta način, so se morali kasneje znebiti praznih besed.

Imena spremenljivk naj bodo takšna, da nekaj povedo o tem, kaj spremenljivka hrani. Imena lst1, lst2 in lst3 namigujejo, da gre za tri sezname, ne vemo pa, sezname česa. (Ob tem se seveda posujem s pepelom: tudi sam sem zgoraj uporabil ime seznam. Res pa sem imel samo en seznam, torej pač vem, za kateri seznam gre. Ko se je pojavil še en, pa je dobil ime izpisani in se tako ločil od pravega, ta pomembnega seznama.)

Začelo se je, česar sem se bal, odkar sem pokazal zanko for: nekateri ste začeli pisati for i in range(len(seznam)): (ali celo for i in range(len(seznam)):), pri čemer pa znotraj zanke i uporabljate samo, da pridete do i-tega elementa, seznam[i], nikoli pa ne potrebujete same vrednosti spremenljivke i. Veliko jasnejše, lepše, hitrejše, bolj Pythonovsko in boljše še v kupu drugih pogledov, ki se jih še ne zavedate, je napisati for beseda in seznam:.

Ena vrstica

Prav, pa se pozabavajmo še s tem starodavnim športom. Za začetek moramo povedati, da rešitev, ki vsebuje podpičja ni rešitev v eni vrstici, temveč rešitev v več vrsticah, zbasanih v eno. Tudi rešitev, ki vsebuje, na primer, for i in msg: (...) ni rešitev v eni vrstici. (Pač pa je dovoljena zanka znotraj list comprehensiona; za tiste, ki veste, kaj je to.)

Poleg tega se dogovorimo, da mora tudi rešitev v eni vrstici vrniti ali izpisati rezultat v natanko takšni obliki, kot ga zahteva naloga.

Tole je ena možnost; pri njej nam ni všeč, da prevečkrat pokliče msg.split() (enkrat za vsako ime, in še enkrat za povrh):

print("\n".join("%s %i" % (m, msg.split().count(m)) for m in set(i for i in msg.split() if i.istitle()))

Tale pa ga pokliče samo enkrat, vendar za ceno tega, da ga shrani v argument lambda funkcije.

print (lambda b: "\n".join("%s %i" % (m, b.count(m)) for m in set(i for i in b if i.istitle())))(msg.split())

Rešitve brez tega gredo v tole smer

print("\n".join("%s %i" % x for x in reduce(lambda x, b:b.istitle() and x.update({b: x.get(b, 0)+1}) or x, msg.split(), {}).items())) kar pa je precej neprebavljivo.

Last modified: Friday, 19 October 2012, 10:20 PM