pre up next title end end

11. Slovníky

  1. Vytvoření slovníku
  2. Slovníkové operace
  3. Slovníkové metody
  4. Sloučení slovníků
  5. Komprehence slovníku
  6. Alias a kopírování
  7. Matice jako slovník
  8. Switch case
  9. Výskyty znaků
    1. Použití počítadla s UCP
    2. Použití slovníku
  10. Výskyty slov
  11. Glosář
  12. Cvičení

11.1 Vytvoření slovníku

Dosud poznané složené datové typy – řetězce, seznamy a entice – jsou sekvence, které používají celá čísla jako indexy pro přístup k jednotlivým položkám.

Slovník (dictionary, dict), je uspořádaná (Python > 3.7) a měnitelná kolekce párových položek key : value. Klíčem může být pouze neměnitelný datový typ, hodnotou libovolný datový typ.

Jako příklad vytvoříme slovník pro překlad anglických slov do španělštiny. Klíči i hodnotami u tohoto slovníku budou řetězce.

Začneme tak, že vytvoříme prázdný slovník, do kterého přidáme párové položky. Prázdný slovník se označuje {}:

>>> en2sp = {}
>>> en2sp['one'] = 'uno'
>>> en2sp['two'] = 'dos'
>>> en2sp
{'one': 'uno', 'two': 'dos'}

První přiřazení vytvoří prázdný slovník s názvem en2sp, další přiřazení přidávají nové párové položky. Párové položky jsou oddělené čárkami.

Jiným způsobem vytvoříme slovník tak, že přímo zadáme výčet slovníkových párů:

>>> en2sp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}

Případně pro vytvoření slovníku použijeme vestavěnou funkci dict(), jejímž argumentem je výpis párů var=val:

>>> di = dict(); di
{}
>>> dj = dict(a=10, b=20); dj
{'a': 10, 'b': 20}
>>> dk = dict([('foo', 100), ('bar', 200)]); dk
{'foo': 100, 'bar': 200}
Poznámka: funkce 'dict()' očekává pouze 1 argument; proto je
nutné výpis entic (u dk) uzavřít do dalšího páru závorek
(typu list, tuple, set).

Hodnoty ve slovníku jsou přístupné prostřednictvím klíčů:

>>> en2sp['two']
'dos'

Klíčový operátor použijeme také pro přidání nové dvojice na konec slovníku:

>>> en2sp['four'] = "quatro"; en2sp
{'one': 'uno', 'two': 'dos', 'three': 'tres', 'four': 'quatro'}

Při výskytu dvou zadaných stejných klíčů se uplatní jen ten později zadaný:

>>> en2sp['two'] = "duo"; en2sp
{'one': 'uno', 'two': 'duo', 'three': 'tres', 'four': 'quatro'}

11.2 Slovníkové operace

Příkaz del odstraní dvojici klíč-hodnota ze slovníku. Následující slovník na příklad obsahuje jména různého druhu ovoce a jeho množství na skladě:

>>> ovoce = {'apples': 430, 'bananas': 312, 'pears': 217, 'oranges': 525}
>>> ovoce
{'apples': 430, 'bananas': 312, 'oranges': 525, 'pears': 217}

Když někdo skoupí všechny hrušky, můžeme tento vstup ze slovníku vyjmout:

>>> del ovoce['pears']; ovoce
{'apples': 430, 'bananas': 312, 'oranges': 525}

Nebo očekáváme-li, že hrušky zase budou, můžeme pouze změnit hodnotu spojenou s hruškami:

>>> ovoce['pears'] = 0; ovoce
{'apples': 430, 'bananas': 312, 'oranges': 525, 'pears': 0}

U slovníku můžeme také použít funkci len, která vrací počet dvojic klíč-hodnota:

>>> len(ovoce)
4

11.3 Slovníkové metody

Slovníky mají řadu užitečných vestavěných metod, jejichž seznam získáme známou funkcí dir():

>>> dir(dict)
['__class__', '...', 'clear', 'copy', 'fromkeys', 'get', 'items',
'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

Metoda .update({key:value}) vloží pár key:value do slovníku:

>>> car = {"značka": "Ford", "model": "Mustang","rok": 1964}
>>> car.update({"color": "black"}); car
{'značka': 'Ford', 'model': 'Mustang', 'rok': 1964, 'color': 'black'}

Metoda .get() přijme klíč a vrátí párovou hodnotu:

>>> car = {"značka": "Ford", "model": "Mustang","rok": 1964}
>>> car.get("model")
'Mustang'

Metoda .items() vrátí výpis párů (klíč, hodnota):

>>> car.items()
dict_items([('značka', 'Ford'), ('model', 'Mustang'), ('rok', 1964)])

Metoda keys přijme slovník a vrátí seznam jeho klíčů:

>>> car.keys()
dict_keys(['značka', 'model', 'rok'])

Metoda values vrátí seznam jeho hodnot:

>>> car.values()
dict_values(['Ford', 'Mustang', 1964])

Výpis klíčů slovníku získáme také idiomem for i in .. :

>>> for i in car:
    print(i, end=' ')
značka model rok 

Výpis hodnot slovníku podobně:

>>> for i in car.(values):
    print(i, end=' ')
Ford Mustang 1964 

K rozbalení klíčů, hodnot i položek slovníku můžeme také použít hvězdičkový operátor:

>>> [*car]
['značka', 'model', 'rok']
>>> [*car.keys()]
['značka', 'model', 'rok']
>>> [*car.values()]
['Ford', 'Mustang', 1964]
>>> [*car.items()]
[('značka', 'Ford'), ('model', 'Mustang'), ('rok', 1964)]

Přítomnost klíče ve slovníku lze ověřit idiomem s klíčovým slovem in:

>>> "model" in car
True
>>> "barva" in car
False

11.4 Sloučení slovníků

Verze Python 3.9.0 přináší dva nové operátory pro sloučení slovníků, sjednocení (dict union) d|e   a rozšířené přiřazení (augmented assignemt) d |= e.

Sjednocení | sloučí obsah obou operandů (zde slovníků). Nachází-li se stejný klíč v obou operandech, pro sloučení se vybere jeho hodnota z pravého operandu - viz případ d|e  a  e|d. Tato operace není komutativní (d|e != e|d):

>>> d = {'spam': 1, 'vejce': 2, 'sýr': 3}
>>> e = {'sýr': 'čedar', 'sele': 'Pepík'}
>>> d | e
{'spam': 1, 'vejce': 2, 'sýr': 'čedar', 'sele': 'Pepík'}
>>> e | d
{'sýr': 3, 'sele': 'Pepík', 'spam': 1, 'vejce': 2}

Rozšířené přiřazení provede přiřazení se sjednocením:

>>> d |= e              # totéž jako d = d | e
>>> d
{'spam': 1, 'vejce': 2, 'sýr': 'čedar', 'sele': 'Pepík'}
>>> e
{'sýr': 'čedar', 'sele': 'Pepík'}

Rozšířené přiřazení lze použít ke změně přiřazené hodnoty:

>>> d |= [('spam', 999)]; d     
{'spam': 999, 'vejce': 2, 'sýr': 'čedar', 'sele': 'Pepík'}

Případně k vložení nového páru:

>>> d |= [('ham', 777)]; d     
{'spam': 999, 'vejce': 2, 'sýr': 'čedar', 'sele': 'Pepík', 'ham': 777}

11.5 Komprehence slovníku

Komprehence slovníku se příliš neliší od komprehence seznamu. Výstupem z komprehence slovníku je slovník, vytvořený aplikací zadaného výrazu pro každý element iteráblu. Schematická skladba je tato:

{key: value for (key [, value]) in iterable}

Iteráblem zde může dle okolností být objekt typu string, list, tuple, range, bytes, bytearray, dict, set, frozentset.

>>> dct_range = {x: x**2 for x in range(5)}; dct_range
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Totéž pomocí mrožího operátoru:
>>> print(dct_range := {x: x**2 for x in range(5)})
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Při komprehenci slovníku ze dvou iteráblů můžeme použít i funkci zip():

>>> keys = ['a', 'b', 'c']
>>> vals = [1, 2, 3]
>>> print(dict_compr_zip := {keys:vals \
...     for (keys,vals) in zip(keys, vals)})
{'a': 1, 'b': 2, 'c': 3}
>>> dict_compr_zip   # vytvořenou proměnnou lze nezávisle použít 
{'a': 1, 'b': 2, 'c': 3}

Slovník vytvoříme i z jednoho iteráblu(tuple, list) funkcemi dict(), zip(), iter():

>>> a = ['hello','world','nomen','omen']
>>> dict(zip(i := iter(a), i))             # zip(i,i)
{'hello': 'world', 'nomen': 'omen'}

11.6 Alias a kopírování

Protože jsou slovníky měnitelné, musíme si dát pozor na aliasování. Kdykoliv dvě proměnné odkazují ke stejnému objektu, jeho změna prostřednictvím jedné proměnné je sdílena i druhou proměnnou.

Chceme-li měnit slovník a přitom si zachovat kopclr-ii originálu, použijeme metodu copy. Například, oppos je slovník, který obsahuje dvojice protikladů:

>>> oppos = {'up': 'down', 'right': 'wrong', 'true': 'false'}
>>> alias = oppos                  id(alias) == id(oppos)
>>> kopie = oppos.copy()           id(kopie) != id(oppos)

Proměnné alias a oppos odkazují na stejný objekt, proměnná kopie odkazuje na nezávislou kopclr-ii téhož slovníku. Upravíme-li alias, změní se i oppos:

>>> alias['right'] = 'left'
>>> oppos['right']
'left'

Když naproti tomu upravíme kopie, tak se oppos nezmění:

>>> kopie['right'] = 'privilege'
>>> oppos['right']
'left'

11.7 Matice jako slovník

K prezentaci matice lze použít seznam seznamů. To je dobrý způsob pro matice s převážně nenulovými hodnotami, uvažme však řídkou matici jako je tato:

Vyjádření řídké matice prostřednictvím seznamu obsahuje množství nul:

matrix = [ [0,0,0,1,0],
           [0,0,0,0,0],
           [0,2,0,0,0],
           [0,0,0,0,0],
           [0,0,0,3,0] ]

Poloha prvku v matici se vyjádří enticí (poř_číslo_řádku,
poř_číslo_sloupce). Číslování počíná nulou.

Alternativou je použití slovníku. Jako klíče můžeme použít entice, obsahující pořadová čísla řádků a sloupců. Zde je slovníkové vyjádření téže matice:

>>> matrix = {(0,3): 1, (2,1): 2, (4,3): 3}

Nenulové prvky matice jsou přístupné pomocí operátoru [ ]:

>>> matrix[0,3]              0,3 je souřadnice mista
1

Pokud však narazíme na nulový prvek matice, dostaneme chybu:

>>> matrix[1,3]
KeyError: (1, 3)

Tento problém řeší metoda get. Prvním argumentem je souřadnice místa, druhým argumentem je "univerzální nula":

>>> matrix.get((0,3),0)
1
>>> matrix.get((1,3),0)
0

11.8 Switch case

Výběrovou proceduru switch case či dispatch lze v Pythonu vytvořit pomocí slovníku. Místo sekvence podmínek if.., elif.., else pro volání existujících funkcí (viz kap. 5.5.4) lze deklarovat slovník, který přiřadí zvolené klíče (lze také použít číslice) k již definovaným funkcím a protože funkce v Pythonu jsou objekty prvního řádu, lze si jednotlivé funkce volat jednoduchým příkazem:

def func_a(): print("fce-a")
def func_b(): print("fce-b")
def func_c(): print("fce-c")

oslík = {"a": func_a, "b": func_b, "c": func_c}

def print_f(value):   # Včetně částečného ošetření výjimek
    oslík.get(value, lambda: 
        print("Neplatné!"))()

Výběr funkce provedeme voláním funkce print_f(value) nebo (protože máme výběrový slovník uveden samostatně) zadáním vybraného klíče slovníku:

>>> print_f("b")
fce-b
>>> print_f(5)
Neplatné

>>> oslík["c"]()     # Kulaté závorky dotvářejí volání funkce.
fce-c 

Volané funkce mohou být také deklarovány jako vložené funkce lambda:

def kalkul(operátor, x, y):
    cases = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b, }
# Deklarované případy:    
    if operátor in cases.keys():
        return cases[operátor](x, y)
# Částečné ošetření výjimek		
    else:
        return cases.get(operátor, "Nenalezeno!") 
>>> kalkul('/', 5, 2)
2.5
>>> kalkul(print, 5, 2)               # Viz poznámka
Nenalezeno!
>>> calcul(^, 5, 3)
SyntaxError: invalid syntax

Poznámka: Zadáme-li jakoukoli pro Python známou hodnotu (mimo deklarované argumenty +, -, *, / ), dozvíme se, že "Nenalezeno". V případě zadání v daném kontextu neznámé hodnoty dojde k chybovému hlášení.

Variace: Funkce lambda lze zajisté nahradit funkcemi pro operace +, -, *, a /. Příslušný výraz se nahradí voláním odpovídající funkce, například:

def add(x, y): return x + y
def sub(x, y): return x - y
def mul(x, y): return x * y
def div(x, y): return x / y
	
def kalk(operátor, x, y):	
    cases = {"+": add, "-": sub, "*": mul, "/": div}
    return cases[operátor](x, y)

Simulaci přepínače lze také vytvořit prostřednictvím třídy.


11.9   Výskyty znaků

V kapitole 6.17, cvičení 2 a 3 jsme počítali výskyt zadaného znaku v zadaném řetězci a to pomocí smyčky s počítadlem, případně přímo prostřednictvím metody 'count'.
Také jsme v kap.9.4 počítali výskyty náhodných čísel v jednolivých úsecích rozpětí 0.0 až 1.0 jako potvrzení skutečnosti, že funkce 'random' modulu 'random' generuje pseudonáhodná čísla.

11.9.1   Použití počítadla s UCP

V následujícím komentovaném programu countLetters.py spočítáme výskyt znaků (písmen) v zadaném textu (Alice in Wonderland) s použitím seznamu counts coby mnohočetného počítadla:

# countLetters.py

# Ošetření tisknutelných (chr(i)) i netisknutelných znaků:

def display(i):                                   
    if i == 10: return 'LF'
    if i == 13: return 'CR'
    if i == 32: return 'SPACE'
    return chr(i)           # vrací tisknutelný znak.
	
Tato funkce bude opakovaně použita v následné smyčce.
Argument 'i' je UCP (Unicode Code Point) zkoumaného znaku.
	
# Načtení textu ze souboru do paměti interpreta: 

inpath = '../files_txt/alice_wonderland.txt'  # cesta k souboru
infile = open(inpath, encoding="utf-8") 
text = infile.read()          # 'text' má formát řetězce
infile.close()

Pro anglický text (kódování ASCii) lze zadat délku seznamu  
counts = 127 * [0], 
pro český text (UTF-8) je nutné zadat counts = 383 * [0]

Případně pokud umíme určit max UCP jako zde:
>>> text = "abcde\n efgh" 
>>> ord(max(text)) --> 104 --> counts = 105 * [0]
můžeme použít seznam kratší.

Položky seznamu 'counts' jsou receptory výskytů jednotlivých 
písmen. Zjištěné hodnoty UCP slouží jako indexy seznamu

# Načtení výskytů do seznamu 'counts':

counts = 127 * [0]                # max UCP pro ASCii = 126
for letter in text:         
    counts[ord(letter)] += 1      # přičítá výskyty znaků
	 # ord(letter) je aktuální index seznamu 'counts'
	                              
# Strukturovaný zápis výskytů do souboru 'letter_counts.dat': 

outpath = '../files_dat/letter_counts.dat'   # deklarace cesty
outfile = open(outpath, 'w')      # otevření/vytvoření souboru

# formátované záhlaví souboru:
outfile.write("%-12s%s\n" % ("Character", "Count"))
outfile.write("=================\n")

# formátovaný obsah pod záhlavím:
for i in range(len(counts)):           # len(counts) = 126+1
    if counts[i]:                        # if counts[i] != 0
        outfile.write("%-12s%d\n" % (display(i), counts[i]))

outfile.close()         # povinné zavření otevřeného souboru

Funkce 'range' v proceduře 'range(len(counts))' 
je důvodem ke zvýšení délky seznmu 'count' o 1.

# Toto oznámení se vytiskne v konzole interpreta IDLE:

>>> print("Frekvenční tabulka je v souboru ", outpath)
>>> print("Délka souboru 'counts' a počet znaků: ", 
                           len(counts), len(text))

# Frekvenční tabulku si prohlédneme v souboru 'letter_counts.dat'.						   

Spusťte si tento program v IDLE a prohlédněte si generovaný výstupní soubor v textovém editoru.

11.9.2   Použití slovníku

Pomocí slovníku vytvoříme seznam výskytů znaků elegantním způsobem:

>>> letter_counts = {}                  # <class 'dict'>
>>> for leclr-tter in "Mississippi":
...     letter_counts[letter] = letter_counts.get (letter, 0) + 1

>>> letter_counts
{'M': 1, 'i': 4, 's': 4, 'p': 2}

Začínáme prázdným slovníkem. Pro každé písmeno v řetězci zvětšíme aktuální četnost o 1. Na konci procesu máme slovník obsahující dvojice písmen a jejich četností.

Bylo by ještě působivější, kdybychom výskyt písmen uspořádali podle abecedy. Můžeme to udělat pomocí univerzální funkce sorted(). Tuto funkci lze použít pro seznamy, řetězce, entice, sety i slovníky:

>>> sorted(letter_counts)
['M', 'i', 'p', 's']
>>> sorted(letter_counts.items())
[('M', 1), ('i', 4), ('p', 2), ('s', 4)]

Proč je M před i ? Vzpomeňte si na funkci ord():

>>> ord("M"), ord("i")
(77, 105)

11.10   Výskyty slov

Výskyt zadaného slova ve vstupním řetězci určíme jednoduše metodou count:

text = "apple mango apple orange orange apple guava\
        mango mango" 
def word_count(text, word):
    print(word + " " + str(text.count(word)))
>>> word_count(text, "mango")
mango 3

Nejjednodušší způsob určení výskytu slov v textu je metodou add a count poté, co jsme si připravili prázdný kolektor typu set a vstupní řetězec konvertovali na seznam:

text = "Woodchuck, how much wood would a woodchuck \
        chuck if a woodchuck could chuck wood ?"

sett = set()              # prázdný kolektor typu 'set'
lstt = text.split()       # konverze stringu na list

for i in lstt:
    sett.add((i, lstt.count(i)))  # 'add' přijímá 1 arg.

S výstupem coby slovník:

>>> print(dict(sett))          # konverze setu na slovník
{'a': 2, 'would': 1, 'how': 1, 'much': 1,
'could': 1, 'chuck': 2, 'Woodchuck,': 1,
'wood': 2, 'woodchuck': 2, '?': 1, 'if': 1}

Případně coby seznam:

>>> print(list(sett))          # konverze setu na seznam
[('?', 1), ('chuck', 2), ('woodchuck', 2), ('would', 1),
('Woodchuck,', 1), ('how', 1), ('wood', 2), ('could', 1),
('much', 1), ('a', 2), ('if', 1)]

11.11 Glosář

slovník (dictionary)
Kolekce dvojic klíč-hodnota, kde klíče ukazují na hodnotu (mapují hodnotu). Klíčem může být jakýkoliv neměnitelný typ, hodnoty mohou být libovolného typu.
dvojice klíč-hodnota (key-value pair)
Párová položka ve slovníku.
invokace (invocation)
Volání metody či funkce
náznak (hint)
Spočítaná a dočasně uložená hodnota, nahrazující zbytečně opakovaný výpočet.
přetečení (overflow)
Číselný výsledek příliš veliký na to, aby mohl být reprezentován číselným formátem.

11.12 Cvičení

  1. Napište funkci, která přijme řetězec ("Až na severní pól šel bych rád") a vrátí frekvenční tabulku, coby slovník s uspořádanými klíči:
    def letter_counts(retiazka):
        ...
    

    Výstup pro "Mississippi" má tvar:

    >>> letter_counts("Mississippi")
    {'M': 1, 'i': 4, 'p': 2, 's': 4}
    
  2. Pokuste se přeformulovat předchozí úlohu tak aby výstupní frekvenční tabulka pro "Mississippi" měla formát:

    M   1
    i   4
    s   4
    p   2
    

    Pro formulaci výstupu můžete použít postup, použitý v kap. 11.8. Nezapomeňte si prohlédnout generovaný výstupní soubor

  3. Uveďte odezvu interpreta v sedmi následujících případech v jedné souvislé seanci:
    1. >>> d = {"apples":15, "bananas":35, "grapes":12}
      >>> d["bananas"]
      
    2. >>> d["oranges"] = 20
      >>> len(d)
      
    3. >>> "grapes" in d
      
    4. >>> d["pears"]
      
    5. >>> d.get("pears", 0)
      
    6. >>> fruits = d.keys()
      >>> sorted(fruits)
      
    7. >>> del d["apples"]
      >>> "apples" in d
      
  4. Sem přijde vymyslet příklad na slovníkový "dispatch" - odstavec 8.
  5. Doplňte tělo funkce, která upravuje obsah slovníku new_inventory:
  6. def add_fruit(inventory, fruit, quantity=0):
        pass
    
    # tyto testy by měly vyjít
    new_inventory = {}
    add_fruit(new_inventory, 'strawberries', 10)
    test('strawberries' in new_inventory, True)
    test(new_inventory['strawberries'], 10)
    add_fruit(new_inventory, 'strawberries', 25)
    test(new_inventory['strawberries'] , 35)
    
  7. Soubor unsorted_fruits.txt obsahuje seznam 26 druhů ovoce, přičemž každé jméno začíná jiným písmenem abecedy.
  8. Napište program sort_fruits.py, který načte ovoce z unsorted_fruits.txt a zapíše je v abecedním pořádku do sorted_fruits.txt.
  9. Napište program alice_words.py, který vytvoří textový soubor alice_words.txt obsahující abecedně uspořádaný výpis slov z alice_in_wonderland.txt spolu s počtem výskytů každého slova. Prvních 10 řádek vašeho výstupu by mohlo vypadat nějak takto:
    Word              Count
    =======================
    a                 631
    a-piece           1
    abide             1
    able              1
    about             94
    above             3
    absence           1
    absurd            2
    
    Kolikrát se v knize objeví slovo alice? Které je nejdelší slovo v Alice in Wonderland? Kolik má písmen?

prev up next title end end