Naloga

Elektronski naslovi

V teh domači nalogi boste morali

  • ločiti med terkami, seznami, slovarji in množicami,
  • razumeti funkcije, kar vam bo pomagalo plavati čez štirinajst dni,
  • znati delati z datotekami in nizi.

Tokrat izjemoma(?) ne bo ogrevalnih, obveznih in dodatnih nalog, ker sem nalogo razdrobil čisto nadrobno (da bi bolj pridno pisali funkcije) in ker je vse dovolj preprosto, da ničemur ne bi rekel "dodatno". Vse je torej obvezno.

V nalogi bomo delali z datotekami, v katerih so imena in priimki ter elektronski naslovi. Tipična vrstica bo torej

Janez Novak      janez.novak@gmail.com
pri čemer je med imenom-in-priimkom in elektronskim naslovom tabulator (\t). Ko boste brali datoteko, bo na koncu vrstice navadno znak za prehod v novo vrstico, \n.

Razčleni

Napiši funkcijo razcleni, ki prejme niz, kakor je zgornji: sestavljen je iz dveh delov, ki sta ločena s tabulatorjem. V prvem delu sta priimek in ime, v drugem naslov. Funkcija naj vrne terko z dvema elementoma - prvi je niz z imenom in priimkom, drugi elektronski naslov. Tako mora, recimo, klic razcleni("Janez Novak\tjanez.novak@gmail.com") vrniti terko ("Janez Novak", "janez.novak@gmail.com").

Iz obeh delov izločite vse morebitne odvečne presledke ali znake za novo vrstico na začetku ali koncu.

Poleg tega poskrbite za primerke, ki nočejo povedati svojega naslova: če naslova ni ali če naslov ne vsebuje znaka @, naj funkcija namesto naslova vrne prazen niz. To naj se zgodi, če pokličemo razcleni("Janez Novak\tne-povem-naslova") ali razcleni("Janez Novak\t") ali razcleni("Janez Novak"). Popazite na vse tri primere.

Končno, lahko se zgodi, da je vrstica prazna oz. vsebuje same presledke ali kaj podobnega. V tem primeru vrnite dva prazna niza, saj nimamo ne imena ne naslova.

Funkcija ni zapletena - samo dva if-a, par return-ov in par klicev metod strip in split. Je pa, zaradi vseh teh pogojev najdaljša (glava + šest vrstic).

Ponudnik

Napiši funkcijo ponudnik(naslov), ki sprejme elektronski naslov in vrne "ponudnika" naslova. Če dobi kot argument, recimo, niz "janez.novak@gmail.com", mora vrniti "gmail.com"; če dobi "janez.novak@pef.uni-lj.si", naj vrne "pef.uni-lj.si".

Predpostaviti smete, da vsebuje niz natančno en znak @. (Da, rešitev je čisto kratka, funkcija ima eno vrstico, poleg glave.)

Branje naslovov

Napiši funkcijo preberi_naslove(datoteka), ki kot argument dobi ime datoteke, kot rezultat pa vrne slovar, katerega ključi so elektronski naslovi, vrednosti pa imena in priimki lastnikov.

Če ji damo, recimo, datoteko

Janez Novak    janez.novak@gmail.com
Peter Piker    peter.piker@hotmail.com
mora vrniti slovar {"janez.novak@gmail.com": "Janez Novak", "peter.piker@hotmail.com": "Peter Piker"}.

Datoteko odprite z open(datoteka, encoding="utf-8") in jo berite z zanko for. Če ne znate, poglejte zapiske.

Funkcija naj ignorira vse vrstice, v katerih ni elektronskih naslovov.

Obstajajo osebe, ki imajo več naslovov. To naj te ne moti: naslovi, ki jih boš uporabil za ključe, so unikatni. Vrednosti se bodo ponavljale, a slovarjev to ne moti.

Namig: stvar je preprosta. Narediš prazen slovar, bereš datoteko, "razčleniš" vsako vrstico in če vsebuje naslov, dodaš v slovar pod ključem naslov ime. Najbrž šest vrstic.

Naslovi po ponudnikih

Napiši funkcijo po_ponudnikih(naslovi), ki kot argument prejme slovar, kakršnega vrača funkcija preberi_naslove (torej: v tej funkciji ne boste poklicali preberi_naslove, temveč jo bo poklical nekdo drug in vam dal rezultat tega klica). Kot rezultat naj vrne slovar, katerega ključi so ponudniki, vrednosti pa množice elektronskih naslovov pri tem ponudniku. Za testne naslove mora vrniti

{'t-3.com': {'Janez Demsar'}, 'hogmail.com': {'Peter Petrovic', 'Peter Z Mailom'}, 'gmail.com': {'Janez Demsar', 'Peter Petrovic', 'Tone Novak', 'Andrej Gorenc'}, 'demsar.com': {'Janez Demsar', 'Peter Petrovic'}, 'aol.com': {'Peter Petrovic'}}

Pazite: gre za slovar, v njem pa so množice (kot vrednosti - ključi pa so nizi). Menda smo imeli nek podoben primer tudi na predavanjih.

Namig: defaultdict. Funkcija sme vrniti tudi defaultdict namesto običajnega slovarja.

Naslovi po ljudeh

Napiši funkcijo po_ljudeh(naslovi), ki prejme naslove v obliki, kot jih vrača preberi_naslove, in vrne slovar, katerega ključi so imena, vrednosti pa množice vse naslovov te osebe. Za testne naslove mora vrniti

{'Janez Demsar': {'janez.demsar@gmail.com', 'janez@demsar.com', 'janez.demsar@t-3.com'}, 'Tone Novak': {'tone.novak@gmail.com'}, 'Peter Petrovic': {'peter-peter@hogmail.com', 'peter2@gmail.com', 'peter@demsar.com', 'se-en-mail@aol.com'}, 'Peter Z Mailom': {'mail@hogmail.com'}, 'Andrej Gorenc': {'andrejgo@gmail.com'}}

Najpopularnejši

Napišite funkcijo najpopularnejsi(s), ki prejme takšen slovar, kakršnega vračata prejšnji dve funkciji (ena ali druga - oba imata za ključe nize, za vrednosti pa množice, le vsebine teh nizov in množic so druge). Če ji damo slovar po ponudnikih, naj funkcija vrne ponudnika, ki ima največ strank (v gornjem primeru mora vrniti "gmail.com". Če ji damo slovar po ljudeh, mora vrniti človeka, ki ima največ naslovo (v gornjem primeru je to "Peter Petrovic".

Namig: delaj se, da delaš prvo, pa boš videl, da bo funkcija "slučajno" zmogla tudi drugo.

Tole je kar poučna naloga, zato jo napiši lepo: funkcija ima glavo in pet vrstic. Vse, kar je več, je odveč.

Skupne stranke

Napiši funkcijo skupne_stranke(ponudniki, ponudnik1, ponudnik2), ki prejme slovar, kakršnega vrača funkcija po_ponudnikih in imeni dveh ponudnikov. Vrne naj množico vseh oseb, ki imajo naslove pri obeh podanih ponudnikih. V gornjem primeru je to Peter Petrovic. (Po domače: računamo presek gmailovih in hogmailovih strank.

Podobnost ponudnikov

Podobnost med dvema ponudnika lahko definiramo kot presek njunih strank deljen z unijo njunih strank. Če imata dva operaterja pet skupnih strank, vseh njunih strank skupaj pa je 20 (s tem, da tiste, ki se pojavijo pri obeh, štejemo samo enkrat), je podobnost med njima 5/20.

Napiši funkcijo jaccard(ponudniki, ponudnik1, ponudnik2). Funkcija prejme enake argumente kot prejšnja funkcija, rezultat pa naj bo podobnost med ponudnikoma.

Predpostaviti smeš, da ima vsaj en ponudnik vsaj eno stranko.

Rešitev

Razčleni

Pri funkcijah, kakršna je razcleni, se nam vedno splača malo razmisliti. Včasih bomo tako že takoj napisali preprosto funkcijo, včasih pa bomo najprej sprogramirali nekaj daljšega in potem poenostavljali.

Takole gre: pokličemo strip in split. Če je rezultat splita dolg le en element (v vrstici ni bilo tabulatorja ali pa je bilo za tabulatorjem vse prazno in ga je še pred splitom odstranil strip) bomo vrnili prvi element in prazen niz.

Če sicer dobimo dva elementa, vendar drugi ne vsebuje znaka @, prav tako vrnemo prvi element in prazen niz. Oba pogoja lahko kar združimo: če imamo manj kot dva elementa ali pa drugi ne vsebuje @, vrnemo prvi element in prazen niz. To mimogrede poskrbi še za primer, ko bi dobili samo prazno vrstico - v tem primeru bo prvi (in edini) element prazen niz, pa bomo lepo vrnili dva prazna niza.

Če do gornih nevšečnosti ne pride, pa vrnemo oba elementa, pred čemer pretvorimo seznam, ki ga je vrnil split, v terko (tuple).

def razcleni(vrstica): vrstica = vrstica.strip().split("\t") if len(vrstica) < 2 or not "@" in vrstica[1]: return vrstica[0], "" return tuple(vrstica)

Ponudnik

Tole je preprosto. Nekateri ste si zagrenili življenje z nepotrebnim find; tudi tule je split priročnejši.

def ponudnik(naslov): return naslov.split("@")[1]

Preberi naslove

Tule le lepo beremo datoteko, vsako vrstico pošljemo v funkcijo razcleni in če drugi element rezultata, naslov, ni prazen niz, shranimo naslov in ime v slovar.

def preberi_naslove(datoteka): naslovi = {} for vrstica in open(datoteka, encoding="utf-8"): ime, naslov = razcleni(vrstica) if naslov: naslovi[naslov] = ime return naslovi

Tisti, ki se bojite klicati lastne funkcije, ste znotraj zanke ponavljali kodo, ki ste jo napisali že v funkciji razcleni. To ni dobro; eden od glavnih ciljev te naloge je bil, da se znebite straha pred klicanjem lastnih funkcij.

Po ljudeh

Tole je precej po zgledu natakarja, s katerim smo se igrali na predavanjih. Spet naredimo slovar, v katerem bodo vrednosti množice, le da tokrat vanj ne zlagamo naročil, ki jih dobimo v nekem seznamu, temveč podatke, ki jih beremo iz slovarja.

Zanko se splača napisati v obliki for naslov, ime in naslovi.items(). Tako iz slovarja dobimo vse pare (naslov, ime). Za vsako ime moramo dodati v pripadajočo množico naslov.

import collections def po_ljudeh(naslovi): ljudje = collections.defaultdict(set) for naslov, ime in naslovi.items(): ljudje[ime].add(naslov) return ljudje

Po ponudnikih

In tole je ravno obratno: za vsak naslov shranimo ime ... le da ne za vsak naslov temveč za vsakega "ponudnika", ki ga dobimo tako, da pokličemo funkcijo ponudnik, ki smo jo napisali zgoraj.

def po_ponudnikih(naslovi): ponudniki = collections.defaultdict(set) for naslov, ime in naslovi.items(): ponudniki[ponudnik(naslov)].add(ime) return ponudniki

Najpopularnejši

Spet gremo z zanko prek slovarja, znotraj nje pa počnemo, kar smo že velikokrat: včasih smo po seznamih iskali največje število, najdaljše ime... tokrat pa iščemo največjo množico (vrednost) in ključ, ki ji pripada.

najvec bo velikost največje množice doslej; v začetku jo postavimo na 0. Potem gremo prek slovarja in ko naletimo na večjo množico od največje doslej, si zapomnimo, za katerega ponudnika (oz. za katerega človeka) gre (naj_k) in koliko strank (oz. naslovov) ima.

def najpopularnejsi(s): najvec = 0 for k, v in s.items(): if len(v) > najvec: naj_k, najvec = k, len(v) return naj_k

Skupne stranke

Naloga je trivialna za vse, ki ste si zapomnili, kaj se da početi z množicami (v Pythonu in tudi sicer). Sprašuje namreč po velikosti preseka dveh množic.

def skupne_stranke(ponudniki, ponudnik1, ponudnik2): return ponudniki[ponudnik1] & ponudniki[ponudnik2]

Jaccard

... je pa isto, samo da velikost preseka delimo še z velikostjo unije.

def jaccard(ponudniki, ponudnik1, ponudnik2): return (len(ponudniki[ponudnik1] & ponudniki[ponudnik2]) / len(ponudniki[ponudnik1] | ponudniki[ponudnik2]))
Zadnja sprememba: ponedeljek, 2. marec 2026, 21.28