previous up next hi end end

11. Slovníky

  1. Slovníkové operace
  2. Slovníkové metody
  3. Sloučení slovníků
  4. Komprehence slovníku
  5. Alias a kopírování
  6. Řídké matice
  7. Switch case
  8. Výskyty znaků
  9. Výskyty slov
  10. Glosář
  11. Cvičení

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íky, řazené mezi kolekce, používají jiný systém pro přístup k položkám. Za jednu položku je považována dvojice klíč:hodnota. 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 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 {}:

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

První přiřazení vytvoří prázdný slovník s názvem eng2sp, další přiřazení přidávají nové párové položky.
Párové položky jsou oddělené čárkami. Každý pár obsahuje klíč a hodnotu, oddělenou dvojtečkou.

Jiným způsobem vytvoříme slovník tak, že přímo zadáme seznam párů klíč-hodnota:

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

Hodnoty ve slovníku jsou přístupné prostřednictvím klíčů, nikoliv indexů.

Takto použijeme klíče k vyhledání odpovídající hodnoty:

>>> eng2sp['two']
'dos'

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

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

11.1 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ě:

>>> inventory = {'apples': 430, 'bananas': 312, 'pears': 217, 'oranges': 525}
>>> inventory
{'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 inventory['pears']
>>> inventory
{'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:

>>> inventory['pears'] = 0
>>> inventory
{'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(inventory)
4

11.2 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)
[__...__, 'clear', 'copy', 'fromkeys', 'get', 'items',
'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

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

>>> car = {"brand": "Ford", "model": "Mustang","year": 1964}
>>> x = car.get("model")
Mustang

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

>>> eng2sp.keys()
dict_keys(['one', 'two', 'three',)

Tato forma tečkové notace uvádí jméno objektu eng2sp a jméno aplikované metody keys. Prázdné závorky naznačují, že tato metoda nepřijímá žádné parametry.

Volání metody se nazývá invokace; v tomto případě bychom řekli, že invokujeme metodu keys pro objekt eng2sp.

Metoda values je podobná; vrací seznam hodnot ve slovníku:

>>> eng2sp.values()
dict_values(['uno', 'dos' 'tres'])

Metoda items vrací obojí formou seznamu entic:

>>> eng2sp.items()
dict_items([('one', 'uno'), ('two', 'dos'), ('three', 'tres')])

Syntaxe poskytuje užitečnou informaci o typech. Hranaté závorky naznačují, že jde o seznam. Oblé závorky naznačují, že položkami seznamu jsou entice.

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

>>> "one" in eng2sp
True
>>> "deux" in eng2sp
False

11.3 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 ten z pravého operandu - viz případ d | e a e | d. Tato operace není komutativní (d | e != e | d):

>>> d = {'spam': 1, 'eggs': 2, 'cheese': 3}
>>> e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
>>> d | e
{'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
>>> e | d
{'cheese': 3, 'aardvark': 'Ethel', 'spam': 1, 'eggs': 2}

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

>>> d |= e                    # totéž jako d = d | e
>>> d
{'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
>>> e
{'cheese': 'cheddar', 'aardvark': 'Ethel'}

Rozšířené přiřazení lze použít k rozšíření slovníku seznamem:

>>> d |= [('spam', 999)]      # nechodí pro d | [('spam', 999)]
>>> d
{'spam': 999, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

11.4 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)}
>>> print(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 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}

11.5 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 kopii 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 kopii 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.6 Řídké matice

V odstavci 9.3 jsme použili seznam seznamů k prezentaci matice. 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í 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] ]

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

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

Potřebujeme jenom tři dvojice klíč: hodnota pro tři nenulové prvky matice. Každý klíč je entice a každá hodnota je celé číslo.

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

>>> matrix[0,3]
1

Všimněme si, že vyjádření matice slovníkem má jinou skladbu než vyjádření matice vnořenými seznamy. Místo dvou celočíselných indexů používáme jenom jeden, jímž je entice z celých čísel.

Je zde však jeden problém. Pokusíme-li se ukázat na nulový prvek matice, dostaneme chybu, protože ve slovníku takový klíč uvedený nemáme:

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

Tento problém řeší metoda get:

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

Prvním argumentem je klíč, druhým argumentem je hodnota, kterou má funkce get vrátit, nebude-li zadávaný klíč ve slovníku:

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

Metoda get rozhodně zlepšuje přístup k prvkům řídké matice.


11.7 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. 4.2.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:

===== RESTART: F:\Codetest\HowTo\ch-09\a9a_dispatch.py =====

>>> 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!") 
============ RESTART: F:\Codetest\HowTo\tremp.py ===========

>>> 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.8   Výskyty znaků

V kapitole 6.19, 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.8 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.

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.

Použití slovníku

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

>>> letter_counts = {}
>>> for letter 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.9   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.10 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.11 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

    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)
    
  6. Soubor unsorted_fruits.txt obsahuje seznam 26 druhů ovoce, přičemž každé jméno začíná jiným písmenem abecedy.
    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.

  7. 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?
  8. Které je nejdelší slovo v Alice in Wonderland? Kolik má písmen?
previous up next hi end end