Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

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í triedy OrderedDict pomocou operátora == vracia True 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ázvom serad_dle_klice().
Oboje je možné uskutočniť pomocou jednoriadkového zápisu:
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:

  1. Chýbajúce kľúč sa pridá do slovníka.
  2. 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

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í.


 

Predchádzajúci článok
Riešené úlohy k 7.-8. lekciu kolekcií v Pythone
Všetky články v sekcii
Kolekcia v Pythone
Preskočiť článok
(neodporúčame)
Riešené úlohy k 9. lekcii kolekcií v Pythone
Článok pre vás napísal synek.o
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Aktivity