pre up next title end end

9. Entice, sety, slovníky

  1. Vytvoření entice
  2. Kopírování entice
  3. Pojmenované entice

  4. Set a frozenset

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

9.1 Vytvoření entice

Entice (tuple) je uspořádaná a 'neměnitelná' kolekce hodnot libovolného typu, oddělených čárkou:

>>> tup = "a", 52, 3.8, [2, 8]     # entice s vnořeným seznamem

Zápis na pravé straně vyhodnotí interpret jako entici (tuple) a jako takovou ji vrátí v závorkách:

>>> tup; type(tup)
('a', 52, 3.8, [2, 8])
<class 'tuple'>

Formát entice má i výraz s jedinou položkou se závěrečnou čárkou:

>>> t1 = 'a',            
>>> t1; type(t1)
('a',)                      # závorky si doplnil interpret
<class 'tuple'>

Bez čárky by byl výraz 'a' považován překladačem Pythonu za řetězec - podobně jako v následující ukázce:

>>> tup_t = "nazdar",; type(tup_t), len(tup_t)
(<class 'tuple'>, 1)
>>> tup_s = "nazdar"; type(tup_s), len(tup_s)
(<class 'str'>, 6)

Entici lze rovněž vytvořit z iteráblu list, set, string, slovník funkcí tuple(), která přijímá jen jeden argument:

>>> tup_lst = tuple([2,3,"a",2]); tup_lst
(2, 3, 'a', 2)
>>> tup_set = tuple({2,3,"a",2}); tup_set
(2, 3, 'a')
>>> tup_str = tuple("ovčín"); tup_str;
('o', 'v', 'č', 'í', 'n')
>>> tup_dct = tuple({"letadlo": "Boeing", "model": "747"}); tup_dct
('letadlo', 'model')

Jako argument funkce tuple() lze použít i literál entice:

>>> tup_ent = tuple(('Jane', (25, 1.75), 'Brno')); tup_ent
('Jane', (25, 1.75), 'Brno')
>>> tup = "a", 52, 3.8, [2, 8]
>>> tup_tup = tuple(tup); tup_tup
('a', 52, 3.8, [2, 8])
# V první ukázce vidíme příklad entice s vnořenou enticí


Odhlédneme-li od skladby, jsou operace s enticemi stejné jako operace se seznamy. Indexový operátor vybere položku z entice:

>>> tup = "a", 52, 3.8, [2, 8]
>>> tup[3][1]
8

Úsekový operátor vybere rozsah položek:

>>> tup[1:3]                  # 1 inclusive, 3 exclusive
(52, 3.8)

Pokusíme-li se změnit některou z indexem vybraných položek, dostaneme chybové hlášení:

>>> tup[0] = 'A'
TypeError: 'tup' object doesn't support item assignment

Neměnitelnost entice znamená, že ji nelze změnit beze změny identity (ID). Pokud nám nevadí změna ID, můžeme entici - jako přiřazenou hodnotu k proměnné, upravit pomocí součtu entic a úsekového operátoru:

>>> ruple = ('a', 'b', 'c', 'd', 'e'); id(ruple)
56396448
>>> ruple = ruple[:2] + ("X",) + ruple[2:]
>>> ruple; id(ruple)
('a', 'b', 'X', 'd', 'e')
63672720

Entici lze ovšem změnit i beze změny ID, pokud měníme její měnitelný člen, například seznam:

>>> mtup = ("a", [3,"b"], True); id(mtup)
1613369562880
>>> mtup[1][0] = 7; mtup; id(mtup)
('a', [7, 'b'], True)
1613369562880                   # ID se nezměnilo

Co s tím? Vést tuto možnost v patrnosti a podle toho se v případě potřeby zařídit.


9.2 Kopírování entice

Entice (tuple) je 'neměnitelná' kolekce prvků. Pokud však entice obsahuje měnitelný element typu list a dict, lze tento prvek změnit obvyklým způsobem a kopírovat metodou copy a deepcopy z modulu copy:

Metoda 'copy' nemění identitu ani u změněného objektu.
Pokud u kopie dojde ke změně, změní se i původní objekt!
>>> import copy >>> lt = ("a", [2.5, "b"]) # vložený seznam >>> slt = copy.copy(lt) # závislá kopie >>> id(lt), id(slt); lt; slt # před změnou (3027328, 3027328) ('a', [2.5, 'b']) ('a', [2.5, 'b']) >>> slt[1][1] = "joj" >>> id(lt), id(slt) ; lt; slt # po změně (3027328, 3027328) ('a', [2.5, 'joj']) ('a', [2.5, 'joj']) Metoda 'deep copy' mění identitu kopie.
Pokud u kopie dojde ke změně, původní objekt se nezmění.
>>> lt = ("a", [2.5, "b"]) >>> dlt = copy.deepcopy(lt) # nezávislá kopie id(lt), id(dlt); lt; dlt # před změnou (3027328, 3039104) ('a', [2.5, 'b']) ('a', [2.5, 'b']) >>> dlt[1][1] = "hoj" >>> id(lt), id(dlt) ; lt; dlt # po změně (3027328, 3039104) ('a', [2.5, 'b']) ('a', [2.5, 'hoj'])

Při úpravě vloženého slovníku musíme zadávat nikoliv index, nýbrž klíč:

>>> import copy

>>> dt = ("a", {2: "b"})              # vložený slovník
>>> sdt = copy.copy(dt)               # závislá kopie
>>> sdt[1][2] = "číč"; dt, sdt
(('a', {2: 'číč'}), ('a', {2: 'číč'}))

>>> dt = ("a", {2: "b"})
>>> ddt = copy.deepcopy(dt)           # nezávislá kopie
>>> ddt[1][2] = "rýč";  dt, ddt
>>> (('a', {2: 'b'}), ('a', {2: 'rýč'}))

Závislou kopii jak prosté, tak složené entice lze rovněž vytvořit úsekovým operátorem [:]

>>> tup = ('a', 'b', 'c')
>>> rup = tup[:]; rup
('a', 'b', 'c')
>>> id(tup), id(rup)
(46444232, 46444232)

dt = ("a", {2: "b"})
>>> sdt = dt[:]; sdt
('a', {2: 'b'})
>>> id(dt), id(sdt)
(46497384, 46497384)

Obšírnější popis kopírování viz kap. 11.4.


9.3 Pojmenované entice

Pojmenovaná entice (named tuple) je neměnitelná entice, opatřená jménem a názvy položek. Prvky entice jsou tedy kromě indexů přístupné také přes jejich slovní označení.
Práce s pojmenovanou enticí připomíná práci s deklarovanou třídou a jejími instancemi. Třídou je zde "výchozí" entice, vytvořená pomocí importované produkční (factory) funkce namedtuple() z modulu collections:

Název_typu = namedtuple('Název_typu', < výpis polí> 
        [, rename=False] [, defaults=None] [, module=None])

Z takto obecně deklarované entice (třídy) vytvoříme jednotlivé entice (instance) s konkrétními hodnotami příslušných položek. Užitečnost pojmenované entice spočívá v tom, že si nemusíme pamatovat význam jednotlivých polí.

Práci s pojmenovanou enticí si nejlépe ukážeme na konkrétním příkladě, ve kterém si vytvoříme kartotéku se jmény účastníků setkání, jejich bydliště a věk:

# nt_person.py - pojmenované entice - named tuples - NT

from collections import namedtuple

## Vytvoření výchozí entice s určením polí (atributů):

# název_NT = namedtuple('název_NT', 'názvy .. polí')
Person = namedtuple('Person', 'jméno, místo, věk')
# Výpis zadaných polí entice (v Thonny):
'''>>> print(Person._fields)         # _fields je metoda NT                 
   ('jméno', 'místo', 'věk')'''

## Vytvoření jednotlivých entic (instancí): 

# <jméno_pol> = <název_NT>(<hodnoty jednotlivých atributů>)
eva = Person('Eva Novotná', 'Brno', věk= 18)
tim = Person('Tim Brown', 'Brno', 18)
'''>>> print(eva); print(tim)
   Person(jméno='Eva Novotná', místo='Brno', věk=18)
   Person(jméno='Tim Brown', místo='Brno', věk=18)'''

## Přístup k jednotlivým polím je trojí (výstupy viz Thonny):

'''>>> print(eva[1])                  # přes index
 >>> print(eva.místo)                 # přes klíč
 >>> print(getattr(eva, 'místo'))'''  # přes fci getattr()

## Vytvoření dalších instancí NT s použitím  funkce _make(~) 
   a operátoru **

li = ['Jan Veselý','Zlín','22']               # list i tuple 
jan = Person._make(li)
'''>>> print(Person._make(li))               
Person(jméno='Jan Veselý', místo='Zlín', věk='22')'''

di = {'jméno':'Hugo Brom','místo':'Kolín','věk':'28'}
hugo = Person(**di)  
'''>>> print(Person(**di))                    # dict
Person(jméno='Hugo Brom', místo='Kolín', věk='28')'''

## Konverze instance z entice na slovník:

'''>>> print(eva._asdict())
   {'jméno': 'Eva Novotná', 'místo': 'Brno', 'věk': 18}'''

## Dodatečné operace, podporované formátem 'named tuples':

# _fields - vrací klíčová slova deklarované NT:
'''>>> print(hugo._fields)
    'jméno', 'místo', 'věk')'''

# _replace - přechodně změní hodnotu pole deklarované NT:
'''>>> print(eva._replace(věk=21))
    Person(jméno='Eva Novotná', místo='Brno', věk=21)'''

# __new__() - vytvoří novou instanci 'třídy' Person:
'''>>> sam = Person.__new__(Person,'Samuel Vorel', 'Tábor', 27); sam
    Person(jméno='Samuel Vorel', místo='Tábor', věk=27)'''

# __getnewargs__() - vrátí NT jako prostou entici:
'''>>> print(hugo.__getnewargs__())                      
    ('Hugo Brom', 'Kolín', 28)'''    # 'hugo' musí být aktivní

Vytvoření seznamu pojmenovaných entic. Předchozí skript doplníme o následující kód:

print('-'*32)                         # dekorace textu       
emp_list = [eva, tim, jan, hugo]
for emp in emp_list:
    print( "%-15s bydliště %s" % (emp.jméno, emp.místo) )
print('-'*32)                         # dekorace textu 
>>> %Run nt_person.py
--------------------------------
Eva Novotná     bydliště Brno
Tim Brown       bydliště Brno
Jan Veselý      bydliště Zlín
Hugo Brom       bydliště Kolín
Samuel Vorel    bydliště Tábor
--------------------------------

Podporované metody

Kromě prezentovaných metod _replace(), _make(), _asdict(), _fields podporuje named tuple stejné metody jako regulérní typ tuple - min(), max(), len(), in, not in, concatenation, index, slice, atd.

Nevýhodou pojmenované entice oproti normální entici je to, že NT je výrazně pomalejší.


9.4 Set a frozenset

Set (množina) je neuspořádaná měnitelná kolekce odlišných prvků, frozenset je neměnitelná kolekce odlišných prvků. Používají se při testování "členství" a při eliminaci duplikátních zápisů.

Kolekce set a frozenset podporují množinové operace sjednocení (union), průnik (intersection), rozdíl (difference) a doplněk (symmetric_difference), funkci len(s) a idiomy x in s, x not in s.

Set vytvoříme buď výčtem jeho prvků nebo pomocí funkce set(~), frozenset vytvoříme pouze funkcí frozenset(~). Obě funkce přijímají pouze jeden argument, jímž může být seznam, řetězec, entice, částečně i slovník. Pozice jednotlivých prvků setu i frozensetu není indexována (object is not subscriptable).

>>> basket = {'apple','orange','apple','pear','orange',
... 'banana'}; basket                    # deklarace výčtem
{'orange', 'apple', 'banana', 'pear'}    # žádné duplikáty!
>>> song = set("Okolo Hradce"); song     # deklarace funkcí
{'o', 'O', 'c', 'd', 'l', 'e', 'a', 'k', ' ', 'r', 'H'}

>>> tup = (1,2,3,"alpha",2)              # jiný příklad
>>> set_tup = set(tup); set_tup
{'alpha', 1, 2, 3}
>>> fro_set_tup = frozenset(tup); fro_set_tup
frozenset({'alpha', 1, 2, 3})      

Použijeme-li jako argument pro funkce set a frozenset slovník, použijí se pouze hodnoty jeho klíčů:

>>> dic = {"a": 1, "b": 2, "c": 3}
>>> set_dic = set(dic); set_dic
{'c', 'a', 'b'}
>>> fro_set_dic = frozenset(dic); fro_set_dic
frozenset({'c', 'a', 'b'})

Set i frozenset mají definovanou částečně společnou řadu metod:

set_meth = add, clear, copy, difference, difference_update, 
discard, intersection, intersection_update, isdisjoint, issubset, 
issuperset, pop, remove, symmetric_difference,  union, update,
symmetric_difference_update

frozenset_meth = copy, difference, intersection, isdisjoint, 
issubset, issuperset, symmetric_difference,  union 

Objekt typu set nepodporuje změnu položky. Složení setu lze nicméně měnit metodami add, clear, discard, pop, remove a update.

Ukážeme si některé operace na setech:

>>> a = set('abracadabra'); a
{'a', 'c', 'b', 'r', 'd'}
>>> b = set('alacazam'); b
{'a', 'c', 'z', 'm', 'l'}
>>> 
>>> a-b, b-a                            # rozdíl
({'b', 'r', 'd'}, {'z', 'l', 'm'})
>>> a | b                               # sjednocení
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b                               # průnik
{'a', 'c'}
>>> a ^ b                               # doplněk
{'r', 'd', 'b', 'm', 'z', 'l'}

Poznámka: Metoda a.update(b) vrací stejný výsledek jako sjednocení a | b, který se stává novou hodnotou setu a:

Šikovná je redukce hromady čísel na jedinečnou řadu a posléze přeměna na posloupnost čísel:

>>> e = (1,2,3,2,1,2,3,4,5,6,5,6,4,7,8,3,2,1,3,4,5,6)
>>> l = list(set(e)); l
[1, 2, 3, 4, 5, 6, 7, 8]

Stejně jako pro seznam lze i pro set definovat komprehenci setu:

>>> s = {v for v in "ABCDABCD" if v not in "CB"}
>>> s
>>> {'A', 'D'}

Zápisy (), [], {} označují práznou entici, seznam a slovník; zápisy set() a frozenset() (nikoliv {}) označují prázdný set a frozenset.


9.5 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 alternativní výpis párů var -> val:

>>> di = dict()

Funkce dict() očekává pouze 1 argument; proto je
nutné výpis entic (u dt, ds, dl) uzavřít do dalšího páru
závorek (typu tuple, set a list).

>>> dt = dict((('foo', 100), ('bar', 200))); dt  # dict(tuple)
{'foo': 100, 'bar': 200}                
>>> ds = dict({('foo', 100), ('bar', 200)}); ds  # dict(set)
{'bar': 200, 'foo': 100}                                      
>>> dl = dict([('foo', 100), ('bar', 200)]); dl  # dict(list)
{'foo': 100, 'bar': 200}                         
>>> dj = dict(a=10, b=20); dj                    # dict(what?)             
{'a': 10, 'b': 20}                      

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

9.6 Slovníkové operace

Procedura 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

Vestavěná funkce reversed() vytvoří iterátor, který iteruje od konce slovníku směrem k jeho počátku.

>>> ovoce = {"ořech": 3, "banán": 2, "švestka": 5}
>>> for plod in reversed(ovoce):
        print(plod, end=" ")
švestka banán ořech
>>> ovoce
{'ořech': 3, 'banán': 2, 'švestka': 5} 

9.7 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

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

9.9 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'}

9.10 Kopírování slovníku

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 pro slovník, zvanou 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'] = 'seno'
>>> oppos['right']
'left'

9.12 Switch case, dispatch

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.3) 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)          # v kontextu známá hodnota
NameError: name 'kalkul' is not defined
>>> calcul(^, 5, 3)              # v kontextu neznámá hodnota
SyntaxError: invalid syntax

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

import math

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)
	
# >>> kalk('+', 9, 3)            --> 12

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


9.13   Výskyty znaků

V kapitole 6.13, 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 8.10 a 8.11 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.

9.13.1   Použití počítadla s UCP

UCP (Unicode Code Point) viz kap. 1.7.

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.
Před realizací programu je vhodné vyčlenit dedikovanou složku (například "F:/Codetest/HowToPY/Alice"), do níž umístíme náš program countLetters.py, námi stažený soubor alice_wonderland.txt a programem vytvořenou frekvenční tabulku letter_counts.dat.
Zkoumaný text získáme z adresy
"https://howto.py.cz/resources/alice_wonderland.txt"
--> RSC (right single click) --> View page source --> RSC --> Save page as --> . . . /Alice/ --> Close.
Soubor letter_counts.dat vytvoříme následujícím programem:

# countLetters.py

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

'''
Tato funkce bude opakovaně použita v následné smyčce.
Argument 'i' je UCP (Unicode Code Point) zkoumaného znaku.
'''

def display(i):                                   
    if i == 10: return 'LF'
    if i == 13: return 'CR'
    if i == 32: return 'SPACE'
    return chr(i)               
	
# Načtení textu ze souboru do paměti interpreta: 

inpath = 'alice_wonderland.txt'           # cesta k souboru
infile = open(inpath, encoding="utf-8") 
text = infile.read()          
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]'''

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

counts = 127 * [0]                   # máme angl. text
for letter in text:         
    counts[ord(letter)] += 1         # ord(letter) vrací UCP
	 	                              
# Strukturovaný zápis výskytů do souboru 'letter_counts.dat': 
                                     # deklarace cesty:
outpath = 'F:/Codetest/HowToPY/Alice/letter_counts.dat'   
outfile = open(outpath, 'w')         # otevř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 'counts' 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í tabulka - viz soubor 'letter_counts.dat'.						   

Spusťte si tento program v IDE Thonny a prohlédněte si generovaný výstupní soubor 'letter_counts.dat' v textovém editoru.

9.13.2   Použití slovníku

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

>>> počet_znaků = {}                  # <class 'dict'>
>>> for znak in "Mississippi":
        počet_znaků[znak] = počet_znaků.get (znak, 0) + 1

>>> počet_znaků
{'M': 1, 'i': 4, 's': 4, 'p': 2}

Začínáme prázdným slovníkem. 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(počet_znaků.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)

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

9.15 Glosář

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.
dvojice klíč-hodnota (key-value pair)
Párová položka ve slovníku.

9.16 Cvičení

  1. Generujte náhodné číslo mezi low a high.
  2. Generujte náhodné celé číslo mezi low a high včetně.
  3. Předpokládá se, že čísla, generovaná systémovou funkcí random jsou rovnoměrně rozdělena, to znamená, že výskyt každé hodnoty má stejnou pravděpodobnost.
    Zapište upravenou funkci countB1(n,b) z konce odstavce 9.9 do souboru histogram.py a vyzkoušejte volání pro n = 2000, 4000, 8000, b = 8. Ověřte, zda četnosti spějí k rovnoměrnénu výskytu.
  4. Do stejného souboru připište upravenou funkci countB2(n,b) z konce odstavce 9.10. Volejte ji pro stejné hodnoty jako v předchozí úloze.
  5. Vytvořte soubor lists_one.py (kap. 8), vložte do něj následující doctesty a doplňte vhodnými definicemi funkcí:
    def make_empty(seq):
        """
        >>> make_empty([1, 2, 3, 4])
        []
        >>> make_empty(('a', 'b', 'c'))
        ()
        >>> make_empty("No, not me!")
        ''
        """
    
    def insert_at_end(val, seq):
        """
        >>> insert_at_end(5, [1, 3, 4, 6])
        [1, 3, 4, 6, 5]
        >>> insert_at_end('x', 'abc')
        'abcx'
        >>> insert_at_end(5, (1, 3, 4, 6))
        (1, 3, 4, 6, 5)
        """
    
    def insert_in_front(val, seq):
        """
        >>> insert_in_front(5, [1, 3, 4, 6])
        [5, 1, 3, 4, 6]
        >>> insert_in_front(5, (1, 3, 4, 6))
        (5, 1, 3, 4, 6)
        >>> insert_in_front('x', 'abc')
        'xabc'
        """
    
    def index_of(val, seq, start=0): """ >>> index_of(9, [1, 7, 11, 9, 10]) 3 >>> index_of(5, (1, 2, 4, 5, 6, 10, 5, 5)) 3 >>> index_of(5, (1, 2, 4, 5, 6, 10, 5, 5), 4) 6 >>> index_of('y', 'happy birthday') 4 >>> index_of('banana', ['apple', 'banana', 'cherry', 'date']) 1 >>> index_of(5, [2, 3, 4]) -1 >>> index_of('b', ['apple', 'banana', 'cherry', 'date']) -1 """
    def remove_at(index, seq): """ >>> remove_at(3, [1, 7, 11, 9, 10]) [1, 7, 11, 10] >>> remove_at(5, (1, 4, 6, 7, 0, 9, 3, 5)) (1, 4, 6, 7, 0, 3, 5) >>> remove_at(2, "Yomrktown") 'Yorktown' """
    def remove_val(val, seq): """ >>> remove_val(11, [1, 7, 11, 9, 10]) [1, 7, 9, 10] >>> remove_val(15, (1, 15, 11, 4, 9)) (1, 11, 4, 9) >>> remove_val('what', ('who', 'what', 'when', 'where', 'why', 'how')) ('who', 'when', 'where', 'why', 'how') """
    def remove_all(val, seq): """ >>> remove_all(11, [1, 7, 11, 9, 11, 10, 2, 11]) [1, 7, 9, 10, 2] >>> remove_all('i', 'Mississippi') 'Msssspp' """ def count(val, seq): """ >>> count(5, (1, 5, 3, 7, 5, 8, 5)) 3 >>> count('s', 'Mississippi') 4 >>> count((1, 2), [1, 5, (1, 2), 7, (1, 2), 8, 5]) 2 """ def reverse(seq): """ >>> reverse([1, 2, 3, 4, 5]) [5, 4, 3, 2, 1] >>> reverse(('shoe', 'my', 'buckle', 2, 1)) (1, 2, 'buckle', 'my', 'shoe') >>> reverse('Python') 'nohtyP' """ def sort_sequence(seq): """ >>> sort_sequence([3, 4, 6, 7, 8, 2]) [2, 3, 4, 6, 7, 8] >>> sort_sequence((3, 4, 6, 7, 8, 2)) (2, 3, 4, 6, 7, 8) >>> sort_sequence("nothappy") 'ahnoppty' """ if __name__ == "__main__": import doctest doctest.testmod()
  6. Odjíždíte na báječnou dovolenou ve středu, to je ve 3. dnu v týdnu. Vrátíte se po 137 nocích. Napište program, který se zeptá na pořadové číslo dne vašeho odjezdu, délku pobytu a vrátí vám název dne, ve kterém se vrátíte. Bude to neděle?
    Použijete vstup z klávesnice, operátor modulo, den návratu vyberete indexem.
    Případně můžete napsat funkci 'návrat(odjezd, délka)'.
  7. 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}
    
  8. 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

  9. 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
      
  10. Sem přijde vymyslet příklad na slovníkový "dispatch" - odstavec 8.
  11. Doplňte tělo funkce, která upravuje obsah slovníku new_inventory:
  12. 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)
    
  13. Soubor unsorted_fruits.txt obsahuje seznam 26 druhů ovoce, přičemž každé jméno začíná jiným písmenem abecedy.
  14. 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.
  15. 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?
pre up next title end end