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:
Vytvoření instance Counter:
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:
Dotaz na chybějící klíč vrací nulu:
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:
Aplikace metody update():
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:
Aplikace metody most_common():
[('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:
Aplikace metody elements():
['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:
Aplikace metody total():
13
Súhrnný príklad
Teraz spojíme získané vedomosti do krátkeho programu, ktorý zanalyzuje
text v súbore muj_neznamy_text.txt
:
import re from collections import Counter vyraz = re.compile(r"\w+") # Vytvoříme si jednoduchý regulární výraz, který pasuje na libovolné slovo with open("muj_neznamy_text.txt", encoding="utf") as soubor: text = soubor.read() # Obsah souboru si uložíme do proměnné text, což je textový řetězec slova = vyraz.findall(text) # Metoda findall() vyhledá všechna slova v řezězci a vytvoří z nich seznam slova_counter = Counter(slova) # Vytvoříme instanci třídy Counter a jako argument dosadíme seznam slov print(f"Zdrojový text obsahuje celkem {slova_counter.total()} slov. Zde je pět s nejvyšším výskytem:") for slovo, pocet in slova_counter.most_common(5): print(f'Slovo: "{slovo}" (Počet: {pocet})')
Výstupom v konzole bude:
Výstup analýzy textu:
Zdrojový text obsahuje celkem 62 slov. Zde je pět s nejvyšším výskytem:
Slovo: "Python" (Počet: 6)
Slovo: "je" (Počet: 5)
Slovo: "populární" (Počet: 4)
Slovo: "programovací" (Počet: 3)
Slovo: "jazyk" (Počet: 2)
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 usporadany_slovnik = 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
.
atributy_dict = dir(dict()) atributy_OrderedDict = dir(OrderedDict()) print(set(atributy_OrderedDict) - set(atributy_dict))
Výstupom v konzole bude:
Atributy jedinečné pro 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:
usporadany_slovnik.move_to_end("Sly") print(usporadany_slovnik) usporadany_slovnik.move_to_end("Leo", last=False) print(usporadany_slovnik)
Výstupom v konzole bude:
Aplikace metody move_to_end():
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
usporadany_slovnik
ako metódu s názvomserad_dle_klice()
.
usporadany_slovnik.serad_dle_klice = lambda: sorted(usporadany_slovnik.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 herec in usporadany_slovnik.serad_dle_klice(): print(herec + " --> " + usporadany_slovnik[herec])
Výstupom v konzole bude:
Aplikace vlastní metody serad_dle_klice():
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úce 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á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 zamestnanci = [ ("Jan Novák", "Výroba"), ("Michaela Modrá", "Obchod"), ("Pavlína Peterková", "Prodej"), ("Karel Nový", "Výroba"), ("Petr Blažek", "Obchod") ]
Ď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):
oddeleni = 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 zamestnanec, pozice in zamestnanci: oddeleni[pozice].append(zamestnanec) print(oddeleni)
Vo výstupe vidíme:
Takto pracuje defaultdict:
defaultdict(class 'list', {'Výroba': ['Jan Novák', 'Karel Nový'], 'Obchod': ['Michaela Modrá', 'Petr Blažek'], 'Prodej': ['Pavlína Peterková']})
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í.