Obsah
1. Funkcionální programování v Pythonu s využitím knihovny Toolz
4. Nekonečné sekvence a líné vyhodnocování
5. Základní operace se sekvencemi
6. Nekonečná sekvence s hodnotami vytvářenými s využitím uživatelské funkce
7. Vygenerování Fibonacciho posloupnosti s využitím iterate
8. Kombinace většího množství sekvencí s využitím funkce interleave
9. Funkce interleave a nekonečné sekvence
10. Rozdělení prvků sekvence do skupin s využití funkce groupby
11. Groupby a nekonečné sekvence
12. Rozdělení prvků sekvence do většího množství sekvencí pomocí partition
13. Rozdělení nekonečné sekvence
14. Proložení prvků sekvence další hodnotou
15. Klasická funkce reduce i její paralelní varianta
16. Postupná redukce vstupu s akumulací mezivýsledků
17. Výpočet rozdílu mezi sekvencemi
18. Rozdíly mezi větším množstvím sekvencí
19. Repositář s demonstračními příklady
1. Funkcionální programování v Pythonu s využitím knihovny Toolz
V sérii článků o funkcionálním programování realizovaném v Pythonu jsme si popsali většinu vlastností standardní knihovny functools, která do Pythonu přináší některé prvky funkcionálního programování. Ovšem další funkcionální vlastnosti tato knihovna z různých důvodů nepokrývá či pokrývá jen částečně. Jedná se jak o podporu kompozice funkcí, popř. curryingu (i když partial je částečným (sic) řešením), tak i například o podporu pro neměnitelné datové typy, funkcí pro zpracování sekvencí, podporu typů Option či Result (tak jsou pojmenované v Rustu) atd. Z tohoto důvodu vznikla celá řada více či méně obsáhlých balíčků, z nichž mnohé jsou vyjmenovány (a podle oblíbenosti seřazeny) na stránce Awesome functional Python. Již jsme se ostatně s jednou z těchto knihoven seznámili. Připomeňme si, že se jednalo o knihovnu nazvanou funcy.
V osmé, tedy dnešní části již však knihovnu funcy opustíme. Ve stručnosti si popíšeme knihovnu nazvanou toolz, jejíž zdrojové kódy lze nalézt na GitHubu na adrese https://github.com/pytoolz/toolz. Tato knihovna je v komunitě vývojářů v Pythonu poměrně populární a mezi její přednosti patří minimální závislosti, malá velikost a logické rozdělení nabízených funkcí do několika podbalíčků. Oproti již popsané knihovně funcy sice (podle všech předpokladů) nalezneme několik rozdílů, ovšem základní koncepty a techniky funkcionálního programování jsou vývojářům pochopitelně stále k dispozici.
2. Instalace knihovny toolz
Instalace knihovny toolz se nijak neliší od instalace jakéhokoli jiného běžného balíčku pro Python. Pro instalaci můžeme použít například dnes již prakticky standardní nástroj pip, popř. pip3. Povšimněte si, že knihovna toolz nemá žádné další závislosti a navíc je na dnešní poměry velmi malá – její velikost při stahování dosahuje jen 56 kB a po rozbalení cca 290 kB:
$ pip install --user toolz Collecting toolz Downloading toolz-0.12.1-py3-none-any.whl.metadata (5.1 kB) Downloading toolz-0.12.1-py3-none-any.whl (56 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 56.1/56.1 kB 921.9 kB/s eta 0:00:00 Installing collected packages: toolz Successfully installed toolz-0.12.1
Po instalaci si přímo v interpretru Pythonu (REPL) ověříme, jestli je balíček toolz dostupný:
import(toolz) help(toolz)
Po vykonání těchto dvou příkazů by se měla zobrazit nápověda ke knihovně toolz:
Help on package toolz: NAME toolz PACKAGE CONTENTS _signatures _version compatibility curried (package) dicttoolz functoolz itertoolz recipes sandbox (package) utils FUNCTIONS reduce(...) reduce(function, sequence[, initial]) -> value Apply a function of two arguments cumulatively to the items of a sequence, from left to right, so as to reduce the sequence to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). If initial is present, it is placed before the items of the sequence in the calculation, and serves as a default when the sequence is empty. sorted(iterable, /, *, key=None, reverse=False) Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order. VERSION 0.12.1 FILE /home/ptisnovs/.local/lib/python3.12/site-packages/toolz/__init__.py
3. Podbalíček itertoolz
Prvním podbalíčkem z knihovny toolz, s nímž se v dnešním článku seznámíme, je podbalíček nazvaný přímočaře itertoolz. Nalezneme zde funkce pracující se sekvencemi a kolekcemi. Těchto funkcí je několik desítek a ty nejdůležitější z nich si popíšeme v navazujícím textu:
accumulate | getter | merge_sorted | reduceby |
collections | groupby | no_default | remove |
concat | heapq | no_pad | rest |
concatv | interleave | nth | second |
cons | interpose | operator | sliding_window |
count | isdistinct | partial | tail |
diff | isiterable | partition | take |
drop | iterate | partition_all | take_nth |
filterfalse | itertools | peek | topk |
first | join | peekn | unique |
frequencies | last | pluck | zip_longest |
get | mapcat | random_sample |
4. Nekonečné sekvence a líné vyhodnocování
Knihovna Toolz dokáže pracovat s konečnými sekvencemi a sekvencemi nekonečnými. Pro první typ sekvencí se někdy používá označení seznam (list), pro druhý pak proud (stream) – to je však v Pythonu poněkud matoucí. Koncept nekonečných sekvencí sice není v oblasti programovacích jazyků žádnou žhavou novinkou, ovšem do praktické podoby byl dopracován až relativně pozdě. Velmi dobrým příkladem programovacího jazyka, jenž je na tomto konceptu do značné míry postaven, je jazyk Clojure, jímž se postupně inspirují další programovací jazyky.
V knihovně Toolz a speciálně v podbalíčku Itertoolz je definováno poměrně velké množství funkcí, které dokážou pracovat se sekvencemi, ať již se jedná o běžné sekvence (jejichž prvky jsou přímo uloženy v operační paměti), nebo takzvané líné sekvence (lazy sekvence), které nové prvky vytváří či zjišťují až při konkrétním přístupu na tyto prvky. Díky tomu, že všechny standardní kolekce (seznamy, n-tice, …) jsou současně i sekvencemi, lze tyto funkce aplikovat i na kolekce, ovšem ve skutečnosti jsou sekvencemi i další typy objektů, zejména pak I/O proudy.
V dalších kapitolách tento koncept uvidíme. Například si necháme proložit prvky konečné a nekonečné sekvence a z výsledku (ten není ihned počítán – Toolz je „líná knihovna“) si necháme vypsat pouze některé vybrané prvky.
5. Základní operace se sekvencemi
Mezi šest základních operací se sekvencemi (a může se jednat o libovolné sekvence, nikoli tedy pouze například seznamy) patří tyto operace (funkce):
Operace | Stručný popis |
---|---|
first | vrátí první prvek ze sekvence |
last | vrátí poslední prvek ze sekvence (pokud je konečná) |
take | vrátí prvních n prvků ze sekvence |
drop | vrátí sekvenci bez prvních n prvků |
nth | vrátí n-tý prvek ze sekvence |
get | vrátí n-tý prvek ze sekvence, výběr ze slovníku podle klíče, dokáže pracovat s neznámými hodnotami |
Pojďme si nyní ve stručnosti tyto operace ukázat. Příklady jsou tak jednoduché, že si namísto jejich popisu pouze uvedeme vypočtené výsledky.
První prvek sekvence:
from toolz.itertoolz import first values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print(first(values))
Výsledek:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] A
Poslední prvek sekvence:
from toolz.itertoolz import last values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print(last(values))
Výsledek:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] Z
Prvních n prvků sekvence:
from toolz.itertoolz import take values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print(list(take(10, values)))
Výsledek:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
Sekvence bez prvních n prvků:
from toolz.itertoolz import drop values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print(list(drop(10, values)))
Výsledek:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] ['K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
Získání n-tého prvku sekvence:
from toolz.itertoolz import nth values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print(nth(0, values)) print(nth(25, values)) print(nth(26, values))
Výsledky:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] A Z Traceback (most recent call last): File "seq_nth.py", line 8, in <module> print(nth(26, values)) File "/home/ptisnovs/.local/lib/python3.8/site-packages/toolz/itertoolz.py", line 396, in nth return seq[n] IndexError: list index out of range
Stejná operace, ale provedená pomocí get s uvedením výchozí hodnoty pro neznámé prvky:
from toolz.itertoolz import get values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print(get(0, values, "?")) print(get(25, values, "?")) print(get(26, values, "?"))
Výsledky:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] A Z ?
6. Nekonečná sekvence s hodnotami vytvářenými s využitím uživatelské funkce
Poměrně často se setkáme s tím, že další prvek sekvence je možné odvodit (pouze) z prvku předchozího na základě nějakého výrazu či složitějšího výpočtu (realizovaného například pomocí funkce). Příkladem mohou být aritmetické, geometrické a další řady. V takovém případě lze pro vytvoření nekonečné sekvence použít funkci nazvanou příznačně iterate z balíčku toolz.itertoolz. Této funkci se zadává hodnota počátečního prvku v sekvenci a taktéž funkce, které se předá hodnota n-tého prvku a výsledkem bude hodnota prvku n+1. Výpočet prováděný v této funkci může být zcela obecný (a kromě běžné funkce můžeme pochopitelně použít i uzávěr, tedy i možnost uložení mezivýsledků v uzávěru).
Podívejme se nyní na nejjednodušší možný příklad. Bude se jednat o generátor nekonečné aritmetické řady s krokem 1 a počátečním prvkem s nulovou hodnotou. Výpočet hodnoty n+1 prvku je realizován ve funkci nazvané inc:
from toolz.itertoolz import iterate, take def inc(x): return x+1 sequence = iterate(inc, 0) sequence = take(10, sequence) print(list(sequence))
Výsledky získané po spuštění tohoto skriptu by měly vypadat následovně:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Pochopitelně můžeme v tomto případě použít i alternativní způsob zápisu založeného na anonymní funkci, což je kratší (a poněkud méně čitelné):
from toolz.itertoolz import iterate, take sequence = iterate(lambda x: x+1, 0) sequence = take(10, sequence) print(list(sequence))
Ve třetím příkladu s funkcí iterate je ukázána řada numerických hodnot obsahujících celočíselné mocniny dvojky:
from toolz.itertoolz import iterate, take def double(x): return x*2 sequence = iterate(double, 1) sequence = take(10, sequence) print(list(sequence))
A takto budou vypadat vypočtené výsledky uložené do výsledné sekvence:
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
Taktéž si můžeme nechat vygenerovat sekvenci seznamů, v nichž postupně roste počet prvků (i jejich hodnoty):
from toolz.itertoolz import first, iterate, take def nextval(x): return x + [x[-1]+1] sequence = iterate(nextval, [1]) sequence = take(10, sequence) print(list(sequence))
Výsledky jsem pro větší přehled ručně zarovnal:
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
7. Vygenerování Fibonacciho posloupnosti s využitím iterate
Poněkud komplikovanější je výpočet Fibonacciho posloupnosti s využitím stejné funkce, tedy funkce iterate. V tomto případě totiž musíme pracovat s dvojicí prvků s indexy n-1 a n pro výpočet prvku s indexem n+1. To sice funkce iterate přímo neumožňuje, ale je zde možné aplikovat malý trik – budeme předávat dvojici hodnot v jediném parametru typu n-tice (nebo seznam, dokonce by bylo možné použít i množinu):
from toolz.itertoolz import iterate, take def one_step(p): return (p[1], p[0]+p[1]) sequence = iterate(one_step, (0, 1)) sequence = take(10, sequence) print(list(sequence))
Tento demonstrační příklad vytiskne sekvenci dvojic prvků. Povšimněte si, že první hodnoty dvojic skutečně tvoří Fibonacciho posloupnost (a nutno dodat, že druhé prvky dvojic vlastně taky, pouze je posloupnost o jeden prvek posunuta):
[(0, 1), (1, 1), (1, 2), (2, 3), (3, 5), (5, 8), (8, 13), (13, 21), (21, 34), (34, 55)]
Malou úpravou tedy z této sekvence získáme skutečnou Fibonacciho posloupnost. Úprava spočívá v aplikaci funkce first na všechny dvojice:
from toolz.itertoolz import first, iterate, take def one_step(p): return (p[1], p[0]+p[1]) sequence = iterate(one_step, (0, 1)) sequence = take(10, sequence) print(list(map(first, sequence)))
Výsledek:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
8. Kombinace většího množství sekvencí s využitím funkce interleave
Další užitečnou funkcí, se kterou se v dnešním článku seznámíme, je funkce s názvem interleave. Jak již název této funkce naznačuje, provádí se zde operace proložení prvků z několika vstupních sekvencí do sekvence jediné. Přitom vstupem mohou být jak konečné, tak i nekonečné sekvence. Zkusme nejprve použít konečné sekvence:
from toolz.itertoolz import interleave, take values1 = range(1, 11) values2 = ["odd", "even"]*5 interleaved = interleave((values1, values2)) print(list(interleaved))
Výsledkem proložení je tato nová sekvence:
[1, 'odd', 2, 'even', 3, 'odd', 4, 'even', 5, 'odd', 6, 'even', 7, 'odd', 8, 'even', 9, 'odd', 10, 'even']
Otestujme si chování pro nestejně dlouhé sekvence:
from toolz.itertoolz import interleave, take values1 = range(1, 16) values2 = ["odd", "even"]*3 interleaved = interleave((values1, values2)) print(list(interleaved))
V tomto případě se nenahlásí chyba, pouze je výsledná sekvence doplněna prvky delší sekvence:
[1, 'odd', 2, 'even', 3, 'odd', 4, 'even', 5, 'odd', 6, 'even', 7, 8, 9, 10, 11, 12, 13, 14, 15]
9. Funkce interleave a nekonečné sekvence
Zajímavé bude zjistit chování funkce interleave pro nekonečné sekvence. Necháme si tedy proložit nekonečnou sekvenci s aritmetickou řadou celých čísel se sekvencí obsahující konečné množství prvků „odd“ a „even“. Výsledkem je nekonečná líná sekvence, z níž si necháme vypsat prvních dvacet prvků:
from toolz.itertoolz import iterate, interleave, take def inc(x): return x+1 values1 = iterate(inc, 0) values2 = ["odd", "even"]*5 interleaved = interleave((values1, values2)) print(list(take(20, interleaved)))
Výsledek by měl vypadat následovně:
[0, 'odd', 1, 'even', 2, 'odd', 3, 'even', 4, 'odd', 5, 'even', 6, 'odd', 7, 'even', 8, 'odd', 9, 'even']
10. Rozdělení prvků sekvence do skupin s využití funkce groupby
Funkce nazvaná groupby (pozor – zde se zapisuje bez podtržítka) dokáže rozdělit původní sekvenci prvků do několika dalších sekvencí, přičemž rozdělení je prováděno na základě nějaké hodnoty vypočtené z každého prvku (na rozdíl od funkce typu filter či remove se zde tedy nejedná o predikát vracející pouze pravdivostní hodnotu, ale o funkci vracející libovolnou spočetnou hodnotu).
Poměrně dobrým a přitom krátkým demonstračním příkladem může být rozdělení sekvence s celočíselnými hodnotami 0..99 do deseti sekvencí na základě poslední (nejnižší) cifry hodnoty. V jedné výsledné sekvenci tedy budou hodnoty končící na nulu, v další hodnoty končící na jedničku atd. (výsledkem je mapa, jak ostatně uvidíme dále):
from toolz.itertoolz import groupby values = range(100) grouped = groupby(lambda x:x % 10, values) for key, values in grouped.items(): print(key, values)
Výsledkem bude deset sekvencí. V první sekvenci budou hodnoty bezezbytku dělitelné deseti, ve druhé sekvenci hodnoty se zbytkem 1 atd.:
0 [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] 1 [1, 11, 21, 31, 41, 51, 61, 71, 81, 91] 2 [2, 12, 22, 32, 42, 52, 62, 72, 82, 92] 3 [3, 13, 23, 33, 43, 53, 63, 73, 83, 93] 4 [4, 14, 24, 34, 44, 54, 64, 74, 84, 94] 5 [5, 15, 25, 35, 45, 55, 65, 75, 85, 95] 6 [6, 16, 26, 36, 46, 56, 66, 76, 86, 96] 7 [7, 17, 27, 37, 47, 57, 67, 77, 87, 97] 8 [8, 18, 28, 38, 48, 58, 68, 78, 88, 98] 9 [9, 19, 29, 39, 49, 59, 69, 79, 89, 99]
Funkci groupby můžeme použít například i pro seskupení slov v textu podle jejich délky. Text nejdříve rozdělíme na jednotlivá slova (resp. přesněji řečeno na sekvenci slov) a následně použijeme group_by, přičemž seskupení vstupních prvků bude vázáno na hodnotu vypočtenou výrazem len(prvek):
from toolz.itertoolz import groupby message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() grouped = groupby(lambda x:len(x), words) for key, values in grouped.items(): print(key, values)
Výsledkem získaným po spuštění tohoto skriptu bude mapa obsahující jako klíče hodnoty len(prvek) a jako hodnoty sekvenci slov s touto délkou:
5 ['Lorem', 'ipsum', 'dolor', 'amet,', 'elit,', 'magna'] 3 ['sit', 'sed'] 11 ['consectetur'] 10 ['adipiscing', 'incididunt'] 2 ['do', 'ut', 'et'] 7 ['eiusmod'] 6 ['tempor', 'labore', 'dolore', 'aliqua']
Praktičtější však bude mapu při jejím výpisu setřídit, takže se nejdříve vypíšou nejkratší slova, potom delší slova atd.:
from toolz.itertoolz import groupby message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() grouped = groupby(lambda x:len(x), words) for key in sorted(grouped.keys()): print(key, grouped[key])
Výsledek:
2 ['do', 'ut', 'et'] 3 ['sit', 'sed'] 5 ['Lorem', 'ipsum', 'dolor', 'amet,', 'elit,', 'magna'] 6 ['tempor', 'labore', 'dolore', 'aliqua'] 7 ['eiusmod'] 10 ['adipiscing', 'incididunt'] 11 ['consectetur']
11. Groupby a nekonečné sekvence
Ve čtvrté kapitole jsme si řekli, že knihovna Toolz podporuje práci s nekonečnými sekvencemi. Aby se tyto sekvence mohly zpracovávat, je nutné realizovat líné vyhodnocování, tedy výpočet prvků sekvencí až ve chvíli, kdy je to skutečně nutné. Ovšem funkce groupby do této skupiny nepatří – není tedy líná. Ostatně si to můžete sami otestovat spuštěním následujícího příkladu:
from toolz.itertoolz import groupby, iterate, take def inc(x): return x+1 values = iterate(inc, 0) grouped = groupby(lambda x:x % 10, values)
Tento příklad bude pouze doplňovat další a další prvky do mapy generované funkcí groupby a skončí ve chvíli, kdy již nebude k dispozici dostatek operační paměti (takže příklad raději po pár sekundách zabijte příkazem kill, ať se nezačne swapovat).
12. Rozdělení prvků sekvence do většího množství sekvencí pomocí partition
Další funkcí, s níž se v dnešním článku seznámíme, je funkce nazvaná partition. Tato funkce rozdělí vstupní sekvenci do (obecně libovolného) množství výstupních sekvencí s tím, že tyto výstupní sekvence budou obsahovat n prvků specifikovaných při volání partition. Navíc je možné řídit, jakým způsobem se bude pracovat s prvky na konci vstupní sekvence, pokud její délka není celočíselně dělitelná onou hodnotou n. Ostatně si to ukažme na příkladu:
from toolz.itertoolz import partition values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print(list(partition(3, values))) print(list(partition(3, values, "*")))
První volání funkce partition rozdělí znaky abecedy po trojicích, ovšem vynechá písmena na konci, které již nedokážou vytvořit trojici. Namísto toho druhé volání doplní znaky o „*“ a žádný vstupní znak se tak neztratí:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'H', 'I'), ('J', 'K', 'L'), ('M', 'N', 'O'), ('P', 'Q', 'R'), ('S', 'T', 'U'), ('V', 'W', 'X')] [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'H', 'I'), ('J', 'K', 'L'), ('M', 'N', 'O'), ('P', 'Q', 'R'), ('S', 'T', 'U'), ('V', 'W', 'X'), ('Y', 'Z', '*')]
13. Rozdělení nekonečné sekvence
Funkce partition popsaná v předchozí kapitole dokáže rozdělit i nekonečnou sekvenci, pochopitelně za předpokladu, že se nebudeme snažit zpracovat celý (nekonečný) výsledek. Můžeme si to snadno otestovat na sérii těchto kroků:
- Vytvoříme nekonečnou sekvenci celých čísel
- Rozdělíme tuto sekvenci do trojic (je jich taktéž nekonečně mnoho)
- Přečteme z této sekvence prvních 10 trojic
- Ty si necháme vypsat
Tyto kroky lze velmi snadno realizovat:
from toolz.itertoolz import iterate, partition, take def inc(x): return x+1 values = iterate(inc, 0) print(list(take(10, partition(3, values))))
A takto může vypadat výsledek:
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23), (24, 25, 26), (27, 28, 29)]
14. Proložení prvků sekvence další hodnotou
Další funkcí, s níž se dnes seznámíme, je funkce nazvaná interpose. Tato funkce dokáže proložit prvky ze vstupní sekvence se zadanou hodnotou. To znamená, že výsledkem bude sekvence obsahující první prvek ze vstupní sekvence, zadanou hodnotu, druhý prvek ze vstupní sekvence, znovu zadanou hodnotu atd. Můžeme si například nechat proložit znaky abecedy hvězdičkou:
from toolz.itertoolz import interpose values = [chr(i) for i in range(ord('A'), ord('Z')+1)] print(values) print("".join(interpose("*", values)))
Takto bude vypadat výsledek:
A*B*C*D*E*F*G*H*I*J*K*L*M*N*O*P*Q*R*S*T*U*V*W*X*Y*Z
Lze ovšem pracovat i s nekonečnými sekvencemi:
from toolz.itertoolz import iterate, interpose, take def inc(x): return x+1 values = iterate(inc, 0) print(list(take(20, interpose("*", values))))
Nyní bude prvních dvacet prvků výsledné sekvence vypadat následovně:
[0, '*', 1, '*', 2, '*', 3, '*', 4, '*', 5, '*', 6, '*', 7, '*', 8, '*', 9, '*']
15. Klasická funkce reduce i její paralelní varianta
V knihovně Toolz nalezneme i klasickou funkci reduce, která zde existuje kvůli snaze o zpětnou kompatibilitu. Ve skutečnosti se jedná o shodnou funkci, jakou nalezneme v balíčku functools. Použití tedy již známe:
from toolz import reduce from toolz.itertoolz import take def one_step(p, q): print(p, q) return p*q result = reduce(one_step, range(1, 11), 1) print(result)
Výsledkem bude hodnota faktoriálu pro n=10:
1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Zajímavější je paralelní varianta funkce reduce, která je umístěna v podbalíčku sandbox.parallel. Tato varianta umožňuje souběžné mezivýpočty a tím pádem nezaručuje, v jakém pořadí se bude vstupní sekvence zpracovávat. To nám však pro komutativní operace nemusí vadit:
from toolz.sandbox.parallel import fold from toolz.itertoolz import take def one_step(p, q): print(p, q) return p*q result = fold(one_step, range(1, 11), 1) print(result)
16. Postupná redukce vstupu s akumulací mezivýsledků
Funkce accumulate má chování podobné výše popsané funkci reduce, ovšem s jedním podstatným rozdílem – zatímco v případě použití funkce reduce se dozvíme pouze celkový výsledek aplikace vybrané funkce na celou sekvenci, je u funkce accumulate vrácena sekvence všech mezivýsledků, čehož je možné v některých případech využít a opět tak eliminovat potřebu tvorby programových smyček. Vypočtěme si tedy tabulku faktoriálu pro n=1 až n=10:
from toolz.itertoolz import accumulate, take def one_step(p, q): print(p, q) return p*q sequence = accumulate(one_step, range(1, 11)) print(list(sequence))
Sekvence na konci skutečně obsahuje tabulku faktoriálů:
1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
17. Výpočet rozdílu mezi sekvencemi
Potenciálně velmi užitečnou funkcí je funkce nazvaná diff. Jak již jméno této funkce naznačuje, slouží pro zjištění rozdílů mezi vstupními sekvencemi. Ukažme si nejprve, jaký výsledek získáme pro dvě krátké vstupní sekvence, jejichž odpovídající si prvky mají někdy shodnou hodnotu a někdy naopak hodnotu rozdílnou:
from toolz.itertoolz import diff seq1 = [1, 2, 3, 4, 5, 6, 7] seq2 = [1, 0, 3, 4, 9, 9, 0] print(list(diff(seq1, seq2)))
Výsledek obsahuje dvojice hodnot těch prvků, které jsou rozdílné. Nevíme tedy, jaké jsou indexy těchto prvků, ale známe jejich hodnoty:
[(2, 0), (5, 9), (6, 9), (7, 0)]
18. Rozdíly mezi větším množstvím sekvencí
Ve skutečnosti dokáže funkce diff najít rozdíly nikoli pouze mezi dvojicí vstupních sekvencí, ale mezi více sekvencemi. Přitom platí, že postačuje, aby se odlišoval jen jediný prvek na n-tém indexu pro to, aby byl hlášen rozdíl. Opět si to můžeme snadno vyzkoušet, a to na příkladu se třemi vstupními sekvencemi:
from toolz.itertoolz import diff seq1 = [1, 2, 3, 4, 5, 6, 7, 8] seq2 = [1, 0, 3, 4, 9, 9, 0, 8] seq3 = [1, 2, 3, 0, 0, 6, 7, 8] print(list(diff(seq1, seq2, seq3)))
Povšimněte si, že hned v prvním případě jsou prvky ze dvou vstupních sekvencí shodné a liší se jen jeden prvek ze sekvence seq2. I to postačuje, aby byl detekován rozdíl:
[(2, 0, 2), (4, 4, 0), (5, 9, 0), (6, 9, 6), (7, 0, 7)]
19. Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si prozatím v tomto seriálu ukázali, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalovánu některou z podporovaných verzí Pythonu 3, a pro dnešní příklady i výše zmíněnou knihovnu funcy):
20. Odkazy na Internetu
- Getting started with functional programming in Python using the toolz library
https://opensource.com/article/18/10/functional-programming-python-toolz - Toolz module in Python
https://www.geeksforgeeks.org/toolz-module-in-python/ - functools — Higher-order functions and operations on callable objects
https://docs.python.org/3/library/functools.html - Functional Programming HOWTO
https://docs.python.org/3/howto/functional.html - Functional Programming in Python: When and How to Use It
https://realpython.com/python-functional-programming/ - Functional Programming With Python
https://realpython.com/learning-paths/functional-programming/ - Awesome Functional Python
https://github.com/sfermigier/awesome-functional-python - Currying
https://en.wikipedia.org/wiki/Currying - Currying in Python – A Beginner’s Introduction
https://www.askpython.com/python/examples/currying-in-python - Fundamental Concepts in Programming Languages
https://en.wikipedia.org/wiki/Fundamental_Concepts_in_Programming_Languages - When should I use function currying?
https://stackoverflow.com/questions/24881604/when-should-i-use-function-currying - Toolz
https://github.com/pytoolz/toolz/tree/master - Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/ - A HITCHHIKER'S GUIDE TO functools
https://ep2021.europython.eu/media/conference/slides/a-hitchhikers-guide-to-functools.pdf - Coconut aneb funkcionální nadstavba nad Pythonem (2.část)
https://www.root.cz/clanky/coconut-aneb-funkcionalni-nadstavba-nad-pythonem-2-cast/ - Knihovny pro zpracování posloupností (sekvencí) v Pythonu
https://www.root.cz/clanky/knihovny-pro-zpracovani-posloupnosti-sekvenci-v-pythonu/ - clj – repositář s knihovnou
https://github.com/bfontaine/clj - clj 0.1.0 – stránka na PyPi
https://pypi.python.org/pypi/clj/0.1.0 - Clojure aneb jazyk umožňující tvorbu bezpečných vícevláknových aplikací pro JVM (4.část – kolekce, sekvence a lazy sekvence)
https://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure a bezpečné aplikace pro JVM: sekvence, lazy sekvence a paralelní programy
https://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - Coconut: Simple, elegant, Pythonic functional programming
http://coconut-lang.org/ - coconut (Python package index)
https://pypi.python.org/pypi/coconut/ - Coconut Tutorial
http://coconut.readthedocs.io/en/master/HELP.html - Coconut FAQ
http://coconut.readthedocs.io/en/master/FAQ.html - Coconut Documentation
http://coconut.readthedocs.io/en/master/DOCS.html - Coconut na Redditu
https://www.reddit.com/r/Python/comments/4owzu7/coconut_functional_programming_in_python/ - Repositář na GitHubu
https://github.com/evhub/coconut - Object-Oriented Programming — The Trillion Dollar Disaster
https://betterprogramming.pub/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7 - Goodbye, Object Oriented Programming
https://cscalfani.medium.com/goodbye-object-oriented-programming-a59cda4c0e53 - So You Want to be a Functional Programmer (Part 1)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-1–1f15e387e536 - So You Want to be a Functional Programmer (Part 2)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-2–7005682cec4a - So You Want to be a Functional Programmer (Part 3)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-3–1b0fd14eb1a7 - So You Want to be a Functional Programmer (Part 4)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-4–18fbe3ea9e49 - So You Want to be a Functional Programmer (Part 5)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-5-c70adc9cf56a - So You Want to be a Functional Programmer (Part 6)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-6-db502830403 - Why Programmers Need Limits
https://cscalfani.medium.com/why-programmers-need-limits-3d96e1a0a6db - Infographic showing code complexity vs developer experience
https://twitter.com/rossipedia/status/1580639227313676288 - Python's reduce(): From Functional to Pythonic Style
https://realpython.com/python-reduce-function/ - What is the problem with reduce()?
https://stackoverflow.com/questions/181543/what-is-the-problem-with-reduce - The fate of reduce() in Python 3000
https://www.artima.com/weblogs/viewpost.jsp?thread=98196 - Reading 16: Map, Filter, Reduce
http://web.mit.edu/6.031/www/sp22/classes/16-map-filter-reduce/ - Currying
https://sw-samuraj.cz/2011/02/currying/ - Používání funkcí v F#
https://docs.microsoft.com/cs-cz/dotnet/fsharp/tutorials/using-functions - Funkce vyššího řádu
http://naucte-se.haskell.cz/funkce-vyssiho-radu - Currying (Wikipedia)
https://en.wikipedia.org/wiki/Currying - Currying (Haskell wiki)
https://wiki.haskell.org/Currying - Haskell Curry
https://en.wikipedia.org/wiki/Haskell_Curry - Moses Schönfinkel
https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel - ML – funkcionální jazyk s revolučním typovým systémem
https://www.root.cz/clanky/ml-funkcionalni-jazyk-s-revolucnim-typovym-systemem/ - Funkce a typový systém programovacího jazyka ML
https://www.root.cz/clanky/funkce-a-typovy-system-programovaciho-jazyka-ml/ - Curryfikace (currying), výjimky a vlastní operátory v jazyku ML
https://www.root.cz/clanky/curryfikace-currying-vyjimky-a-vlastni-operatory-v-jazyku-ml/ - Primer on Python Decorators
https://realpython.com/primer-on-python-decorators/ - Python Decorators
https://www.programiz.com/python-programming/decorator - PythonDecorators (Python Wiki)
https://wiki.python.org/moin/PythonDecorators - Funcy na GitHubu
https://github.com/suor/funcy/ - Welcome to funcy documentation!
https://funcy.readthedocs.io/en/stable/ - Funcy cheatsheet
https://funcy.readthedocs.io/en/stable/cheatsheet.html - PyToolz API Documentation
https://toolz.readthedocs.io/en/latest/index.html - Toolz (PyToolz) na GitHubu
https://github.com/pytoolz/toolz - Fn.py: enjoy FP in Python
https://github.com/kachayev/fn.py - Funcy na PyPi
https://pypi.org/project/funcy/ - Underscore aneb další knihovna pro funkcionální programování v JavaScriptu
https://www.root.cz/clanky/underscore-aneb-dalsi-knihovna-pro-funkcionalni-programovani-v-javascriptu/ - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Awesome functional Python
https://github.com/sfermigier/awesome-functional-python - lispy
https://pypi.org/project/lispy/ - clojure_py na indexu PyPi
https://pypi.python.org/pypi/clojure_py - PyClojure
https://github.com/eigenhombre/PyClojure - Hy na GitHubu
https://github.com/hylang/hy - Hy: The survival guide
https://notes.pault.ag/hy-survival-guide/ - Hy běžící na monitoru terminálu společnosti Symbolics
http://try-hy.appspot.com/ - Welcome to Hy’s documentation!
http://docs.hylang.org/en/stable/ - Hy na PyPi
https://pypi.org/project/hy/#description - Getting Hy on Python
https://lwn.net/Articles/596626/