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řídyOrderedDict
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ázvemserad_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:
- Chybějící klíč se přidá do slovníku.
- 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í.