Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.
IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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

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ázvom sort_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:

  1. Chýbajúci 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

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


 

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