pre up next title end end

9. Entice, sety, slovníky

  1. Vytvoření entice
  2. Kopírování entice
  3. Pojmenované entice
  4.  
  5. Set a frozenset
  6.  
  7. Vytvoření slovníku
  8. Slovníkové operace
  9. Slovníkové metody
  10. Sloučení slovníků
  11. Komprehence slovníku
  12. Kopírování slovníku
  13. Switch case, dispatch
  14. Výskyty znaků
    1. Použití počítadla s UCP
    2. Použití slovníku
  15. Výskyty slov
  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 deepcopy mění identitu kopie.
Pokud u kopie dojde ke změně, původní objekt se nezmění.

>>> import copy
>>> lt = ("a", [2.5, "b"])
>>> dlt = copy.deepcopy(lt)(lt)       # nezávislá kopie 
>>> id(lt), id(slt); lt; dlt          # před změnou
 (3027328, 3039104)
 ('a', [2.5, 'b'])
 ('a', [2.5, 'b'])
>>> dlt[1][1] = "hoj"
>>> id(lt), id(slt); 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')             # prostá entice
>>> rup = tup[:]; rup
('a', 'b', 'c')
>>> id(tup), id(rup)
(46444232, 46444232)

>>> dt = ("a", {2: "b"})              # složená entice
>>> 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

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)    # --> ('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í:

print(eva[1], eva.místo, end= " ")   # přes index a klíč
print(getattr(eva, 'místo'))         # přes fci getattr()
  Brno Brno Brno

## 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))                        # dictionary
  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)     
  
# Seznam pojmenovaných entic :  

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

Ve výše uvedeném textu jsou růžově uvedeny výstupy z jednotlivých příkazů print(). V níže uvedené ukázce jsou výstupy z posledních tří příkazů print().

>>> %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 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); rovněž 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}                     # bez duplicit
>>> 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á 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  # v(tuple)
{'foo': 100, 'bar': 200}                
>>> ds = dict({('foo', 100), ('bar', 200)}); ds  # v(set)
{'bar': 200, 'foo': 100}                                      
>>> dl = dict([('foo', 100), ('bar', 200)]); dl  # v(list)
{'foo': 100, 'bar': 200}                         
>>> dj = dict(a=10,b=20); dj                                 
{'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 ovoce:
        print(plod, end=" ")
 ořech banán švestka
>>> 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.values()
 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'}       # 'e' se nemění

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.

>>> dic_range = {x: x**2 for x in range(5)}; dict_range
 {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Totéž pomocí mrožího operátoru:
>>> print(dic_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.11 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:

# switch_case.py
def func_a: print("fce-a")
def func_b: print("fce-b")
def func_b: print("fce-c")

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

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:

# kalkul_911a.py

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, }
    if operátor in cases.keys():
        return cases[operátor](x, y)
    else:
        return cases.get(operátor, "Nenalezeno!") 
>>> kalkul('/', 5, 2)
2.5
>>> kalkul(print, 5, 2)
'Nenalezeno!'
>>> calcul(^, 5, 3)              
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:

# kalkul_9.11b.py
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.12   Výskyty znaků

V kapitole 6.14, 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.12.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ů v zadaném textu (Alice in Wonderland) s použitím seznamu counts - coby mnohočetného počítadla.
Před realizací programu si vytvoříme dedikovanou složku (například F:/Howtopy/Alice912), do níž umístíme náš program countLetters.py, a programem vytvořenou frekvenční tabulku letter_counts.dat.
Zkoumaný text získáme tak, že na této stránce otevřeme odkaz na "Alice in Wonderland", kde si pravým klikem otevřeme kontextovou nabídku, z níž vybereme "Uložit stránku jako...". Uložíme ji např. jako "alenka.txt" do připravené složky "Alice912".
Soubor letter_counts.py vytvoříme následujícím programem:

# countletters.py
# Výskyt tisknutelných (chr(i)) i netisknutelných znaků

'''
Dále prezetnovaná 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")   # otevření streamu 
text = infile.read()                      # Alenka je seznamu
infile.close()                            # zavření streamu

# Rozdělení textu na slova - lze vypustit
seznam_slov = text.split()
pocet_slov = len(seznam_slov)

''' 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:/Howtopy/Alice912/letter_counts.dat'   
outfile = open(outpath, 'w')         # otevření streamu

# formátované záhlaví souboru 'letter_counts.dat':
outfile.write("%-12s%s\n" % ("Znak", "Počet"))
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í streamu

''' ** 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 v počtu znaků je: ", 
                          len(text))
print("Počet slov v souboru je: ",  len(seznam_slov)) 						  

# Frekvenční tabulka - viz soubor 'letter_counts.dat'.						   

Spusťte si tento program v Thonny a prohlédněte si ve složce F:/Howtopy/ch-09/Alice912 generovaný výstupní soubor letter_counts.dat s výskytem jednotlivých znaků.

>>> %Run countLetters.py

 Frekvenční tabulka je v souboru  F:/Howtopy/ch-09/Alice912/letter_counts.dat
 Délka souboru v počtu znaků je:  148529
 Počet slov v souboru je:  26466

9.12.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.13   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:

# occurrences913.py

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