NOVINKA - Online rekvalifikační kurz Python programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 9 - Counter, OrderedDict a defaultdict v Pythonu

V předešlém cvičení, Řešené úlohy k 7.-8. lekci kolekcí v Pythonu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

V tomto tutoriálu kolekcí v Pythonu si představíme třídy Counter, OrderedDict a defaultdict, které jsou potomky vestavěného slovníku (třídy dict). Techniky probrané v této lekci lze plnohodnotně realizovat klasickým slovníkem. Nicméně, pokud si chceme ulehčit práci a zvýšit čitelnost kódu, tyto specializované třídy nám určitě pomohou.

Counter

Třída Counter je speciální slovník, kde jednotlivé prvky jsou uloženy jako klíče a četnost těchto prvků jako příslušné hodnoty. Jinými slovy klíč reprezentuje daný prvek a hodnota reprezentuje počet výskytů tohoto prvku. Použitím třídy Counter lze tedy snadno a efektivně získat statistiku četnosti prvků v seznamu, řetězci nebo jiném iterovatelném objektu.

V jiných programovacích jazycích se často používá pojem Multiset. Counter je pythonovská implementace tohoto datového typu.

Příklady použití Counter

Ukažme si tři nejběžnější způsoby, jak vytvořit instanci třídy 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)

Ve výstupu vidíme, že všechny tři způsoby dávají stejný výsledek:

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})

Instance třídy Counter po dotazu na chybějící klíč vracejí nulu. Na rozdíl od instancí třídy dict, kde je vyvolána KeyError výjimka:

print(c1["b"])
print(c1["x"])

Výstupem v konzoli bude:

Dotaz na chybějící klíč vrací nulu:
2
0

Metody pro práci s Counter

Třída Counter obsahuje mimo jiné následující metody:

update()

Metoda umožňuje aktualizovat počet prvků v instanci třídy Counter pomocí jiného objektu. Tento objekt může být buď typu Counter, nebo iterovatelný objekt (například seznam, tuple, řetězec nebo slovník). Na rozdíl od stejnojmenné metody předka (dict) se ve chvíli updatu již existujícího klíče jeho hodnota nepřepíše, nýbrž přičte:

c1 = Counter("abbccc")
c1.update("abeceda")
print(c1)

Výstupem v konzoli bude:

Aplikace metody update():
Counter({'c': 4, 'a': 3, 'b': 3, 'e': 2, 'd': 1})

most_common(N)

Metoda vrátí N prvků s nejvyšší četností. Pokud parametr N není uveden, vrátí seznam všech prvků:

print(c1.most_common(3))

Výstupem v konzoli bude:

Aplikace metody most_common():
[('c', 4), ('a', 3), ('b', 3)]

elements()

Tato metoda vrátí iterátor jednotlivých prvků, kde každý prvek se opakuje přesně tolikrát, kolikrát je jeho hodnota (četnost). Abychom na výstupu viděli nějaký smysluplný výsledek, převedeme si ho na seznam:

print(list(c1.elements()))

Výstupem v konzoli bude:

Aplikace metody elements():
['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'e', 'e', 'd']

total()

Metoda total() vrátí součet četností všech prvků dohromady:

print(c1.total())

Výstupem v konzoli bude:

Aplikace metody total():
13

Souhrnný příklad

Nyní spojíme získané vědomosti do krátkého programu, který 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."


# Odstraníme interpunkční znaménka
text = text.translate(str.maketrans("", "", string.punctuation))

# Rozdělíme text na slova podle mezer
slova = text.split()

slova_counter = Counter(slova)

print(f"Zdrojový text obsahuje celkem {slova_counter.total()} slov. Zde je pět slov s nejvyšším výskytem:")

for slovo, pocet in slova_counter.most_common(5):
    print(f'Slovo: "{slovo}" (Počet: {pocet})')

Výstupem v konzoli bude:

Výstup analýzy textu:
Zdrojový text obsahuje celkem 28 slov. Zde je pět slov s nejvyšším výskytem:
Slovo: "ipsum" (Počet: 2)
Slovo: "elit" (Počet: 2)
Slovo: "Lorem" (Počet: 1)
Slovo: "dolor" (Počet: 1)
Slovo: "sit" (Počet: 1)
Slovo: "amet" (Počet: 1)

Kód využívá metodu translate() k odstranění interpunkce z textu. Metoda translate() je metoda řetězce, která umožňuje nahrazovat nebo odstraňovat znaky pomocí tabulky překladu. Tabulka překladu je speciální objekt, který definuje, jaké znaky mají být nahrazeny nebo odstraněny.

Pro vytvoření této tabulky využijeme metodu maketrans() z modulu string. Metoda maketrans() přijímá tři argumenty:

  • První argument obsahuje znaky, které mají být nahrazeny.
  • Druhý argument určuje znaky, kterými budou původní znaky nahrazeny.
  • Třetí argument obsahuje znaky, které mají být odstraněny.

V našem případě ponecháme první dva argumenty jako prázdné řetězce, protože nebudeme žádné znaky nahrazovat. Do třetího argumentu vložíme všechny interpunkční znaky, které chceme odstranit. Tyto znaky získáme pomocí konstanty string.punctuation, která obsahuje všechny běžné interpunkční symboly v Pythonu.

Po vytvoření tabulky překladu ji předáme metodě translate(). Tato metoda pak projde text a odstraní všechny interpunkční znaky (nahrazením prázdným řetězcem).

Výsledkem je řetězec text bez interpunkce.

OrderedDict

Jak už název napovídá, klíčovou vlastností třídy OrderedDict je to, že prvky jsou uloženy v takovém pořadí, v jakém byly postupně do slovníku přidávány. S příchodem Pythonu 3.6 bylo řazení prvků implementováno i do klasického slovníku a mohlo by se zdát, že použití OrderedDict již nedává smysl. Zde jsou argumenty, proč OrderedDict stále svůj smysl má:

  • čitelnost kódu: použitím třídy OrderedDict dáváme jasně najevo, že pořadí prvků je důležité. To se hodí ve chvíli, kdy náš kód čte někdo jiný a nebo pokud se k němu vracíme my sami po delší době,
  • zpětná kompatibilita: pokud chceme garantovat přenositelnost mezi libovolnými verzemi Pythonu, musíme sáhnout po OrderedDict. Pak máme jistotu, že náš program se bude chovat stejně i na systémech s verzemi nižšími jak Python 3.6,
  • význam operátoru ==: porovnání dvou instancí třídy OrderedDict pomocí operátoru == vrací True když obě instance obsahují nejen shodné prvky, ale zároveň i ve stejném pořadí! U klasického slovníku se pořadí ignoruje.

Metody pro práci s OrderedDict

Nejprve si vytvoříme instanci třídy OrderedDict:

from collections import OrderedDict

usporadany_slovnik = OrderedDict(Sly="Rambo", Arnie="Terminator", Leo="Titanic")

dir()

Vestavěná funkce dir() vrací seznam atributů objektu. V kombinaci s množinovým rozdílem můžeme zjistit, které z nich jsou jedinečné pro OrderedDict.

atributy_dict = dir(dict())
atributy_OrderedDict = dir(OrderedDict())

print(set(atributy_OrderedDict) - set(atributy_dict))

Výstupem v konzoli bude:

Atributy jedinečné pro OrderedDict:
{'move_to_end', '__dict__'}

move_to_end(last=True)

Metoda move_to_end() přemístí existující prvek na konec slovníku (pokud last=True a nebo argument není zadán). Pokud last=False, existující prvek je přemístěn na začátek slovníku. V případě, že daný prvek neexistuje, je vyvolána KeyError výjimka:

usporadany_slovnik.move_to_end("Sly")
print(usporadany_slovnik)

usporadany_slovnik.move_to_end("Leo", last=False)
print(usporadany_slovnik)

Výstupem v konzoli bude:

Aplikace metody move_to_end():
OrderedDict([('Arnie', 'Terminator'), ('Leo', 'Titanic'), ('Sly', 'Rambo')])
OrderedDict([('Leo', 'Titanic'), ('Arnie', 'Terminator'), ('Sly', 'Rambo')])

__dict__

Díky tomuto atributu můžeme dynamicky přidávat vlastní metody. Příklad použití vypadá takto:

  • nejprve si pomocí lambda výrazu vytvoříme vlastní funkci, která vrátí seřazený slovník dle klíče,
  • následně ji přidáme objektu usporadany_slovnik jako metodu s názvem serad_dle_klice().

Obojí lze uskutečnit pomocí jednořádkového zápisu:

usporadany_slovnik.serad_dle_klice = lambda: sorted(usporadany_slovnik.keys())

Nyní můžeme pomocí této nové metody vypsat prvky slovníku abecedně dle jména herce (nikoliv dle pořadí, v jakém byly do slovníku postupně přidávány):

for herec in usporadany_slovnik.serad_dle_klice():
    print(herec + " --> " + usporadany_slovnik[herec])

Výstupem v konzoli bude:

Aplikace vlastní metody serad_dle_klice():
Arnie --> Terminator
Leo --> Titanic
Sly --> Rambo

defaultdict

Poslední třídou, se kterou se v této lekci seznámíme, je defaultdict. Třída pracuje zajímavým způsobem. Poskytuje slovník s výchozí hodnotou pro klíče, které ještě neexistují v daném slovníku. Jakmile je tedy přijat požadavek na hodnotu chybějícího prvku (neexistujícího klíče), na místo výjimky KeyError která by vznikla v případě klasického slovníku, se provedou následující dva kroky:

  1. Chybějící klíč se přidá do slovníku.
  2. Zavolá se tzv. tovární metoda a její vrácený objekt se nastaví jako hodnota tohoto klíče.

Tovární metodou může být libovolný volatelný objekt včetně funkcí a tříd. Jejím účelem je vygenerování výchozí hodnoty pro chybějící klíč.

Tovární metoda musí být bezparametrická.

Často se za tovární metody dosazují některé vestavěné funkce. Například int() po zavolání vrací nulu, str() prázdný řetězec a list() prázdný seznam. Pojďme si to ukázat na příkladu. K dispozici máme následující seznam zaměstnanců:

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")
]

Dále si vytvoříme instanci třídy defaultdict. Jako typ pro tovární metodu využijeme seznam a dosadíme tedy list (bez závorek, zatím ji nevoláme):

oddeleni = defaultdict(list)

Nyní můžeme iterovat na původním seznamu a vytvořit slovník jednotlivých oddělení. Metoda append() nevyhodí výjimku ani ve chvíli dotazu na neexistující klíč. To proto, že se nejprve automaticky vytvoří prázdný seznam:

for zamestnanec, pozice in zamestnanci:
    oddeleni[pozice].append(zamestnanec)

print(oddeleni)

Ve výstupu 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 této lekci vše.

V následujícím cvičení, Řešené úlohy k 9. lekci kolekcí v Pythonu, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Jak se ti líbí článek?
Před uložením hodnocení, popiš prosím autorovi, co je špatněZnaků 0 z 50-500
Předchozí článek
Řešené úlohy k 7.-8. lekci kolekcí v Pythonu
Všechny články v sekci
Kolekce v Pythonu
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 9. lekci kolekcí v Pythonu
Článek pro vás napsal synek.o
Avatar
Uživatelské hodnocení:
306 hlasů
Aktivity