9. diel - Counter, OrderedDict a defaultdict v Pythone
V predchádzajúcom cvičení, Riešené úlohy k 7.-8. lekciu kolekcií v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V tomto tutoriále kolekcií v Pythone si predstavíme
triedy Counter
, OrderedDict
a
defaultdict
, ktoré sú potomkami vstavaného slovníka (triedy
dict
). Techniky prebrané v tejto lekcii je možné plnohodnotne
realizovať klasickým slovníkom. Avšak, ak si chceme uľahčiť prácu a
zvýšiť čitateľnosť kódu, tieto špecializované triedy nám určite
pomôžu.
Counter
Trieda Counter
je špeciálny slovník, kde jednotlivé
prvky sú uložené ako kľúče a frekvencia týchto prvkov ako príslušné
hodnoty. Inými slovami kľúč reprezentuje daný prvok a hodnota
reprezentuje počet výskytov tohto prvku. Použitím triedy
Counter
je teda možné ľahko a efektívne získať štatistiku
početnosti prvkov v zozname, reťazci alebo inom iterovateľnom objekte.
V iných programovacích jazykoch sa často používa pojem
Multiset
. Counter
je pythonovská implementácia tohto
dátového typu.
Príklady použitia
Counter
Ukážme si tri najbežnejšie spôsoby, ako vytvoriť inštanciu triedy
Counter
:
from collections import Counter c1 = Counter("abbccc") c2 = Counter(['a', 'b', 'b', 'c', 'c', 'c']) c3 = Counter(a=1, b=2, c=3) print(c1, c2, c3)
Vo výstupe vidíme, že všetky tri spôsoby dávajú rovnaký výsledok:
Creating a Counter instance:
Counter({'c': 3, 'b': 2, 'a': 1}) Counter({'c': 3, 'b': 2, 'a': 1}) Counter({'c': 3, 'b': 2, 'a': 1})
Inštancie triedy Counter
po dotaze na chýbajúci
kľúč vracajú nulu. Na rozdiel od inštancií triedy
dict
, kde je vyvolaná KeyError
výnimka:
print(c1["b"]) print(c1["x"])
Výstupom v konzole bude:
Querying a missing key returns zero:
2
0
Metódy pre prácu s
Counter
Trieda Counter
obsahuje okrem iného nasledujúce metódy:
update()
Metóda umožňuje aktualizovať počet prvkov v inštancii triedy
Counter
pomocou iného objektu. Tento objekt môže byť buď typu
Counter
alebo iterovateľný objekt (napríklad zoznam, tuple,
reťazec alebo slovník). Na rozdiel od rovnomennej metódy predka
(dict
) sa vo chvíli updatu už existujúceho kľúča jeho
hodnota neprepíše, ale pripočíta:
c1 = Counter("abbccc") c1.update("abeceda") print(c1)
Výstupom v konzole bude:
Applying the update() method:
Counter({'c': 4, 'a': 3, 'b': 3, 'e': 2, 'd': 1})
most_common(N)
Metóda vráti N prvkov s najvyššou frekvenciou. Ak
parameter N
nie je uvedený, vráti zoznam všetkých prvkov:
print(c1.most_common(3))
Výstupom v konzole bude:
Applying the most_common() method:
[('c', 4), ('a', 3), ('b', 3)]
elements()
Táto metóda vráti iterátor jednotlivých prvkov, kde každý prvok sa opakuje presne toľkokrát, koľkokrát je jeho hodnota (početnosť). Aby sme na výstupe videli nejaký zmysluplný výsledok, prevedieme si ho na zoznam:
print(list(c1.elements()))
Výstupom v konzole bude:
Applying the elements() method:
['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'e', 'e', 'd']
total()
Metóda total()
vráti súčet početností všetkých
prvkov dohromady:
print(c1.total())
Výstupom v konzole bude:
Applying the total() method:
13
Súhrnný príklad
Teraz spojíme získané vedomosti do krátkeho programu, ktorý zanalyzuje text:
from collections import Counter import string text = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam sapien elit, " \ "consequat eget, tristique non, venenatis quis, ante. Nullam dapibus fermentum " \ "ipsum. Fusce wisi. Mauris dictum facilisis augue." # Remove punctuation marks. text = text.translate(str.maketrans("", "", string.punctuation)) # Split text into words by spaces words = text.split() word_counter = Counter(words) print(f"The source text contains a total of {word_counter.total()} words. Here are the five words with the highest occurrence:") for word, count in word_counter.most_common(5): print(f'Word: "{word}" (Occurrence count: {count})')
Výstupom v konzole bude:
Text analysis output:
The source text contains a total of 28 words. Here are the five words with the highest occurrence:
Word: "ipsum" (Occurrence count: 2)
Word: "elit" (Occurrence count: 2)
Word: "Lorem" (Occurrence count: 1)
Word: "dolor" (Occurrence count: 1)
Word: "sit" (Occurrence count: 1)
Kód využíva metódu translate()
na odstránenie interpunkcie
z textu. Metóda translate()
je metóda reťazca, ktorá umožňuje
nahrádzať alebo odstraňovať znaky pomocou tabuľky prekladu. Tabuľka
prekladu je špeciálny objekt, ktorý definuje, aké znaky majú byť
nahradené alebo odstránené.
Na vytvorenie tejto tabuľky využijeme metódu maketrans()
z
modulu string. Metóda maketrans()
prijíma tri argumenty:
- Prvý argument obsahuje znaky, ktoré majú byť nahradené.
- Druhý argument určuje znaky, ktorými budú pôvodné znaky nahradené.
- Tretí argument obsahuje znaky, ktoré majú byť odstránené.
V našom prípade ponecháme prvé dva argumenty ako prázdne reťazce,
pretože nebudeme žiadne znaky nahradzovať. Do tretieho argumentu vložíme
všetky interpunkčné znaky, ktoré chceme odstrániť. Tieto znaky získame
pomocou konštanty string.punctuation
, ktorá obsahuje všetky
bežné interpunkčné symboly v Pythone.
Po vytvorení tabuľky prekladu ju odovzdáme metóde
translate()
. Táto metóda potom prejde text a odstráni všetky
interpunkčné znaky (nahradením prázdnym reťazcom).
Výsledkom je reťazec text bez interpunkcie.
OrderedDict
Ako už názov napovedá, kľúčovou vlastnosťou triedy
OrderedDict
je to, že prvky sú uložené v takom poradí,
v akom boli postupne do slovníka pridávané. S príchodom Pythona 3.6
bolo radenie prvkov implementované aj do klasického slovníka a mohlo by sa
zdať, že použitie OrderedDict
už nedáva zmysel. Tu sú
argumenty, prečo OrderedDict
stále svoj zmysel má:
- čitateľnosť kódu: použitím triedy
OrderedDict
dávame jasne najavo, že poradie prvkov je dôležité. To sa hodí vo chvíli, keď náš kód číta niekto iný alebo ak sa k nemu vraciame my sami po dlhšej dobe, - spätná kompatibilita: ak chceme garantovať
prenositeľnosť medzi ľubovoľnými verziami Pythonu, musíme siahnuť po
OrderedDict
. Potom máme istotu, že náš program sa bude správať rovnako aj na systémoch s verziami nižšími ako Python 3.6, - význam operátora
==
: porovnanie dvoch inštancií triedyOrderedDict
pomocou operátora==
vraciaTrue
, keď obe inštancie obsahujú nielen zhodné prvky, ale zároveň aj v rovnakom poradí! Pri klasickom slovníku sa poradie ignoruje.
Metódy pre prácu s
OrderedDict
Najprv si vytvoríme inštanciu triedy OrderedDict
:
from collections import OrderedDict ordered_dict = OrderedDict(Sly="Rambo", Arnie="Terminator", Leo="Titanic")
dir()
Vstavaná funkcia dir()
vracia zoznam atribútov objektu. V
kombinácii s množinovým rozdielom môžeme zistiť, ktoré z nich sú
jedinečné pre OrderedDict
.
attributes_dict = dir(dict()) attributes_OrderedDict = dir(OrderedDict()) print(set(attributes_OrderedDict) - set(attributes_dict))
Výstupom v konzole bude:
Attributes unique to OrderedDict:
{'move_to_end', '__dict__'}
move_to_end(last=True)
Metóda move_to_end()
premiestni existujúci prvok na koniec
slovníka (pokiaľ last=True
alebo argument nie je zadaný).
Pokiaľ last=False
, existujúci prvok je premiestnený na
začiatok slovníka. V prípade, že daný prvok neexistuje, je
vyvolaná KeyError
výnimka:
ordered_dict.move_to_end("Sly") print(ordered_dict) ordered_dict.move_to_end("Leo", last=False) print(ordered_dict)
Výstupom v konzole bude:
Applying the move_to_end() method:
OrderedDict([('Arnie', 'Terminator'), ('Leo', 'Titanic'), ('Sly', 'Rambo')])
OrderedDict([('Leo', 'Titanic'), ('Arnie', 'Terminator'), ('Sly', 'Rambo')])
__dict__
Vďaka tomuto atribútu môžeme dynamicky pridávať vlastné metódy. Príklad použitia vyzerá takto:
- najprv si pomocou
lambda
výrazu vytvoríme vlastnú funkciu, ktorá vráti zoradený slovník podľa kľúča, - následne ju pridáme objektu
ordered_dict
ako metódu s názvomsort_by_key()
.
Oboje je možné uskutočniť pomocou jednoriadkového zápisu:
ordered_dict.sort_by_key = lambda: sorted(ordered_dict.keys())
Teraz môžeme pomocou tejto novej metódy vypísať prvky slovníka abecedne podľa mena herca (nie podľa poradia, v akom boli do slovníka postupne pridávané):
for actor in ordered_dict.sort_by_key(): print(actor + " --> " + ordered_dict[actor])
Výstupom v konzole bude:
Applying the custom method sort_by_key():
Arnie --> Terminator
Leo --> Titanic
Sly --> Rambo
defaultdict
Poslednou triedou, s ktorou sa v tejto lekcii zoznámime, je
defaultdict
. Trieda pracuje zaujímavým spôsobom. Poskytuje
slovník s predvolenou hodnotou pre kľúče, ktoré ešte neexistujú v danom
slovníku. Akonáhle je teda prijatá požiadavka na hodnotu chýbajúceho prvku
(neexistujúceho kľúča), na miesto výnimky KeyError
ktorá by
vznikla v prípade klasického slovníka, sa vykonajú nasledujúce dva
kroky:
- Chýbajúci kľúč sa pridá do slovníka.
- Zavolá sa tzv. továrenská metóda a jej vrátený objekt sa nastaví ako hodnota tohto kľúča.
Továrenskou metódou môže byť ľubovoľný volateľný objekt vrátane funkcií a tried. Jej účelom je vygenerovanie východiskovej hodnoty pre chýbajúci kľúč.
Továrenská metóda musí byť bezparametrická.
Často sa za továrenské metódy dosadzujú niektoré vstavané
funkcie. Napríklad int()
po zavolaní vracia nulu,
str()
prázdny reťazec a list()
prázdny zoznam.
Poďme si to ukázať na príklade. K dispozícii máme nasledujúci zoznam
zamestnancov:
from collections import defaultdict employees = [ ("John Smith", "Production"), ("Emily Brown", "Sales"), ("Samantha Johnson", "Retail"), ("Michael Taylor", "Production"), ("David Wilson", "Sales") ]
Ďalej si vytvoríme inštanciu triedy defaultdict
. Ako typ pre
továrenskú metódu využijeme zoznam a dosadíme teda list
(bez
zátvoriek, zatiaľ ju nevoláme):
departments = defaultdict(list)
Teraz môžeme iterovať na pôvodnom zozname a vytvoriť slovník
jednotlivých oddelení. Metóda append()
nevyhodí výnimku ani vo
chvíli dotazu na neexistujúci kľúč. To preto, že sa najskôr automaticky
vytvorí prázdny zoznam:
for employee, department in employees: departments[department].append(employee) print(departments)
Vo výstupe vidíme:
This is how defaultdict works:
defaultdict(class 'list', {'Production': ['John Smith', 'Michael Taylor'], 'Sales': ['Emily Brown', 'David Wilson'], 'Retail': ['Samantha Johnson']})
To je k tejto lekcii všetko.
V nasledujúcom cvičení, Riešené úlohy k 9. lekcii kolekcií v Pythone, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.