Obsah
1. Funkce vyššího řádu určené pro zpracování sekvencí v knihovně funcy
2. Sekvence a lazy sekvence v programovacích jazycích
3. Funkce vyššího řádu filter z knihovny funcy
4. Funkce vyššího řádu lfilter
5. Předání lambda výrazu do funkcí vyššího řádu filter a lfilter
6. Rozšířená „sémantika funkcí“ v knihovně funcy
7. Regulární výraz ve funkci predikátu
8. Množina ve funkci predikátu
9. Funkce vyššího řádu remove a lremove
10. Funkce vyššího řádu map v knihovně funcy
12. Použití regulárního výrazu namísto transformační funkce v lmap
13. Získání unikátních prvků ze vstupní sekvence funkcí distinct
14. Filtrace prvků sekvence funkcí takewhile
15. Filtrace prvků sekvence funkcí dropwhile
16. Rozdělení sekvence na prvky odpovídající predikátu a na prvky, které predikátu neodpovídají
17. Kombinace funkcí vyššího řádu takewhile a dropwhile
18. Rozdělení prvků sekvence do většího množství sekvencí pomocí partition_by
19. Repositář s demonstračními příklady
1. Funkce vyššího řádu určené pro zpracování sekvencí v knihovně funcy
V seriálu o programovacím jazyce Clojure, který na Rootu vyšel, jsme se již mnohokrát setkali s pojmem sekvence, popř. nekonečné sekvence nebo dokonce lazy (líné) sekvence. Připomeňme si, že se jedná o datovou abstrakci, která je sice velmi jednoduchá, ale o to užitečnější v praxi – ostatně velká část standardní knihovny Clojure je na sekvencích založena. Pro ty programátory, kteří programovací jazyk Clojure znají a současně používají i Python, je určena minimalisticky pojatá knihovna nazvaná clj. V této knihovně nalezneme implementaci všech základních funkcí, které jsou v Clojure určeny pro práci se sekvencemi. Tyto funkce je možné použít i pro klasické seznamy a iterátory, jak ostatně uvidíme v dalším textu (i když v poněkud jiném kontextu).
Dnes se ovšem zaměříme nikoli na výše zmíněnou knihovnu clj, ale na knihovnu, které jsme se začali věnovat minule – jedná se o knihovnu s příhodným názvem funcy. I tato knihovna programátorům poskytuje funkce pro práci se sekvencemi, přičemž názvy příslušných operací jsou mnohdy totožné s Clojure či s výše zmíněnou knihovnou clj.
2. Sekvence a lazy sekvence v programovacích jazycích
Naprostý základ pro práci se sekvencemi tvoří trojice funkcí nazvaných first, rest a rest. Funkce first vrací první prvek v sekvenci, popř. speciální hodnotu None v případě, že je sekvence prázdná (pro tyto funkce je typické, že nevyhazují výjimky). Funkce rest vrací zbylé prvky v sekvenci. Běžných sekvencí, například seznamů, mohou být tyto funkce implementovány přímočaře, ovšem v případě lazy sekvencí se prvky vrácené pomocí funkce first vyhodnocují až za běhu, například pomocí nějaké generátorové funkce. Tímto způsobem je možné pracovat i s nekonečnými sekvencemi, u nichž už z principu nelze dopředu znát celkový počet prvků atd.
Další poskytované funkce nad touto abstrakcí staví další vrstvu abstrakce, která umožňuje pracovat se sekvencemi voláním několika funkcí vyššího řádu, navíc bez nutnosti psát explicitně programové smyčky (ty se ostatně píšou resp. spouští špatně pro nekonečné sekvence). Mezi funkce z další vrstvy abstrakce patří zejména:
Operace | Funkce |
---|---|
výběr prvků na základě kritéria | filter, lfilter, remove, lremove |
nová sekvence získaná aplikací nějaké funkce na všechny prvky původní sekvence | map, lmap |
výběr unikátních prvků ze sekvence | distinct |
filtrace s vyhledáním prvního „důležitého“ prvku (zarážky) | takewhile |
filtrace až do prvního „důležitého“ prvku | takewhile |
rozdělení prvků do několika sekvencí | split, split_by, partition_by |
Všechny tyto funkce si dnes popíšeme.
3. Funkce vyššího řádu filter z knihovny funcy
S funkcí vyššího řádu filter jsme se již v tomto miniseriálu setkali, mj. i proto, že se jedná o funkci, která je součástí standardní knihovny programovacího jazyka Python (a dokonce ji nalezneme i ve výchozím jmenném prostoru, takže ji ani nemusíme importovat). Připomeňme si, že tato funkce vybere (vyfiltruje) prvky z nějaké sekvence na základě vyhodnocení takzvaného predikátu, což je funkce, která pro každý prvek vrací pravdivostní hodnotu (resp. v Pythonu libovolnou hodnotu, která je ovšem chápána v pravdivostním kontextu).
Podívejme se na jednoduchý příklad, který již známe:
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = filter(lambda word: len(word) > 4, words) print(filtered) print(list(filtered)) print() filtered = filter(lambda word: len(word) <= 4, words) print(filtered) print(list(filtered))
Výsledky budou vypadat následovně:
<filter object at 0x7f40b0bb2040> ['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua'] <filter object at 0x7f40b0b85c70> ['sit', 'sed', 'do', 'ut', 'et']
I v knihovně funcy nalezneme funkci nazvanou filter. Tato funkce je zdánlivě totožná se standardní funkcí filter, ovšem v dalších kapitolách uvidíme, že jsou její schopnosti ve skutečnosti mnohem větší. Ale pro standardní predikáty i pro stejný vstup získáme naprosto totožné výsledky:
from funcy import filter message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = filter(lambda word: len(word) > 4, words) print(filtered) print(list(filtered)) print() filtered = filter(lambda word: len(word) <= 4, words) print(filtered) print(list(filtered))
Výsledky budou vypadat naprosto stejně, jako v prvním příkladu:
<filter object at 0x7f02645f5040> ['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua'] <filter object at 0x7f02645c8c10> ['sit', 'sed', 'do', 'ut', 'et']
4. Funkce vyššího řádu lfilter
Kromě známé funkce filter nalezneme v knihovně funcy i funkci nazvanou podobně, ovšem s prefixem „l“. Plné jméno této funkce tedy zní lfilter a onen prefix „l“ znamená, že výsledkem činnosti této funkce není generátor, ale plnohodnotný seznam (list). Ostatní vlastnosti ovšem zůstanou zachovány, o čemž se můžeme velmi snadno přesvědčit spuštěním následujícího skriptu:
from funcy import lfilter message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = lfilter(lambda word: len(word) > 4, words) print(filtered) print(list(filtered)) print() filtered = lfilter(lambda word: len(word) <= 4, words) print(filtered) print(list(filtered))
Výsledkem by měl být po obou voláních seznam, takže další převod na seznam standardní funkcí list je již v tomto případě zbytečný:
['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua'] ['sit', 'sed', 'do', 'ut', 'et'] ['sit', 'sed', 'do', 'ut', 'et']
5. Předání lambda výrazu do funkcí vyššího řádu filter a lfilter
Asi nebude velkým překvapením, že jak do funkce filter, tak i do funkce lfilter lze předat libovolný lambda výraz, jehož výsledek je použit pro určení, zda se má právě testovaný prvek předat do výsledné sekvence (či seznamu). Jen pro naprostou úplnost:
from funcy import filter, lfilter data = range(0, 11) print("filter") filtered = filter(lambda value : value %2 == 1, data) print(list(filtered)) filtered = filter(lambda value : value %2 == 0, data) print(list(filtered)) print() print("lfilter") filtered = lfilter(lambda value : value %2 == 1, data) print(filtered) filtered = lfilter(lambda value : value %2 == 0, data) print(filtered)
Výsledky by měly vypadat následovně:
filter [1, 3, 5, 7, 9] [0, 2, 4, 6, 8, 10] lfilter [1, 3, 5, 7, 9] [0, 2, 4, 6, 8, 10]
6. Rozšířená „sémantika funkcí“ v knihovně funcy
Vážený čtenář se pravděpodobně po přečtení předchozí kapitoly ptá, proč je vůbec zdůrazněno, že funkce filter a lfilter akceptují lambda výraz (či běžnou funkci). To je přece naprosto logické a očekávatelné. Ovšem v knihovně funcy je „sémantika funkcí“ u predikátů rozšířena, což znamená, že namísto běžné funkce můžeme do filter či lfilter předat i jinou hodnotu ve funkci predikátu. Jak bude tato hodnota využita je naznačeno v tabulce:
Typ hodnoty | Způsob využití jako predikátu |
---|---|
funkce | klasický predikát volaný s hodnotou aktuálně zpracovávaného prvku |
lambda výraz | klasický predikát volaný s hodnotou aktuálně zpracovávaného prvku |
řetězec | testování prvku s využitím re_tester (tedy řetězec je chápán jako regulární výraz) |
množina | test, zda je zpracovávaný prvek prvkem této množiny |
celé číslo | volá se itemgetter(f) |
7. Regulární výraz ve funkci predikátu
Podívejme se nyní, jak snadné je zadat regulární výraz ve funkci predikátu. Může se dokonce jednat o běžný řetězec, který je vyhledáván ve vstupních prvcích a pokud je nalezen, je prvek vložen do výsledné sekvence a/nebo seznamu (v závislosti na tom, zda se použije filter nebo lfilter). V posledním případě vyhledáváme ta slova, která končí na „or“, ostatní případy vyhledávají sekvenci znaků kdekoli ve slově:
from funcy import lfilter message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = lfilter("et", words) print(filtered) filtered = lfilter("a", words) print(filtered) filtered = lfilter("o", words) print(filtered) filtered = lfilter("or$", words) print(filtered)
Výsledky získané po spuštění tohoto demonstračního příkladu:
['amet,', 'consectetur', 'et'] ['amet,', 'adipiscing', 'labore', 'magna', 'aliqua'] ['Lorem', 'dolor', 'consectetur', 'do', 'eiusmod', 'tempor', 'labore', 'dolore'] ['dolor', 'tempor']
8. Množina ve funkci predikátu
Víme již, že jako predikát lze zadat i množinu. Každý prvek, který je v této množině nalezen, se stane součástí výsledné sekvence (nebo seznamu), takže vlastní způsob použití je triviální:
from funcy import lfilter message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = lfilter({"sit"}, words) print(filtered) filtered = lfilter({"sit", "sed", "do"}, words) print(filtered) filtered = lfilter({"foo", "sed", "bar"}, words) print(filtered)
Výsledky dokazují, jak vše pracuje:
['sit'] ['sit', 'sed', 'do'] ['sed']
Množina je sice použita jako predikát, ovšem ve výsledné sekvenci/seznamu se samozřejmě prvky mohou opakovat (nejedná se o množinu). Zkusme si to na sekvenci slov, kde se jednotlivá slova 3× opakují:
from funcy import lfilter message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" message *= 3 print(message) words = message.split() filtered = lfilter({"sit"}, words) print(filtered) filtered = lfilter({"sit", "sed", "do"}, words) print(filtered) filtered = lfilter({"foo", "sed", "bar"}, words) print(filtered)
Výsledné zprávy ukazují, že ve vytvořených seznamech se prvky skutečně mohou opakovat:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliquaLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliquaLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua ['sit', 'sit', 'sit'] ['sit', 'sed', 'do', 'sit', 'sed', 'do', 'sit', 'sed', 'do'] ['sed', 'sed', 'sed']
9. Funkce vyššího řádu remove a lremove
Funkce filter a lfilter se používají pro vytvoření nové sekvence (nebo seznamu), která bude obsahovat jen ty prvky ze sekvence původní, které odpovídají nějakému predikátu (predikát je obecně funkce vracející pro svůj jediný vstup pravdivostní hodnotu). Funkce remove a lremove pracují přesně naopak, tj. odstraňují ze sekvence/seznamu ty prvky, které odpovídají predikátu.
Otestování funkce remove bude snadné. Nejprve si vyzkoušíme odstranění příliš krátkých či naopak příliš dlouhých slov:
from funcy import remove message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() removed = remove(lambda word: len(word) > 4, words) print(removed) print(list(removed)) print() removed = remove(lambda word: len(word) <= 4, words) print(removed) print(list(removed))
S tímto výsledkem:
<itertools.filterfalse object at 0x7f0d7b3b9040> ['sit', 'sed', 'do', 'ut', 'et'] <itertools.filterfalse object at 0x7f0d7b38cbe0> ['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua']
Namísto funkce remove můžeme použít i funkci lremove s odlišným typem výsledků:
from funcy import lremove message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() removed = lremove(lambda word: len(word) > 4, words) print(removed) print(list(removed)) print() removed = lremove(lambda word: len(word) <= 4, words) print(removed) print(list(removed))
Výsledky:
['sit', 'sed', 'do', 'ut', 'et'] ['sit', 'sed', 'do', 'ut', 'et'] ['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua']
Použití lremove společně s regulárními výrazy namísto predikátů:
from funcy import lremove message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() removed = lremove("et", words) print(removed) removed = lremove("a", words) print(removed) removed = lremove("o", words) print(removed)
Výsledky:
['Lorem', 'ipsum', 'dolor', 'sit', 'adipiscing', 'elit,', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'dolore', 'magna', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'sit', 'consectetur', 'elit,', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'et', 'dolore'] ['ipsum', 'sit', 'amet,', 'adipiscing', 'elit,', 'sed', 'incididunt', 'ut', 'et', 'magna', 'aliqua']
A konečně použití lremove společně s množinami namísto predikátu:
from funcy import lremove message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() removed = lremove({"sit"}, words) print(removed) removed = lremove({"sit", "sed", "do", "amet"}, words) print(removed) removed = lremove({"foo", "sed", "bar"}, words) print(removed)
Výsledky nyní budou vypadat následovně:
['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua']
10. Funkce vyššího řádu map v knihovně funcy
V knihovně funcy nalezneme i klasickou funkci vyššího řádu nazvanou map. Připomeňme si, že základní variantu této funkce již velmi dobře známe, protože je součástí standardního jmenného prostoru Pythonu. Jen pro zopakování si ukažme, jak tato funkce dokáže na jednotlivé prvky vstupní sekvence aplikovat nějakou uživatelem zadanou funkci, například funkci len:
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() lengths = map(len, words) print(lengths) print(list(lengths))
Výsledkem bude speciální objekt, který lze ovšem v případě potřeby převést na seznam:
<map object at 0x7f2cf895d040> [5, 5, 5, 3, 5, 11, 10, 5, 3, 2, 7, 6, 10, 2, 6, 2, 6, 5, 6]
Funkce map z knihovny funcy se bude pro stejné vstupy chovat naprosto totožným způsobem:
from funcy import map message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() lengths = map(len, words) print(lengths) print(list(lengths))
Výsledky:
<map object at 0x7f2500a2f040> [5, 5, 5, 3, 5, 11, 10, 5, 3, 2, 7, 6, 10, 2, 6, 2, 6, 5, 6]
11. Funkce vyššího řádu lmap
Podobně jako existuje funkce filter ve dvou variantách nazvaných filter a lfilter, je tomu podobně i u funkce map, ke které existuje alternativa nazvaná lmap vracející přímo seznam a nikoli speciální objekt „map“. Tuto funkci si opět můžeme velmi snadno otestovat:
from funcy import lmap message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() lengths = lmap(len, words) print(lengths) print(list(lengths))
Výsledkem volání lmap je seznam, takže se po spuštění tohoto skriptu vypíšou dva totožné řádky:
[5, 5, 5, 3, 5, 11, 10, 5, 3, 2, 7, 6, 10, 2, 6, 2, 6, 5, 6] [5, 5, 5, 3, 5, 11, 10, 5, 3, 2, 7, 6, 10, 2, 6, 2, 6, 5, 6]
12. Použití regulárního výrazu namísto transformační funkce v lmap
I u funkcí map a lmap lze namísto klasické transformační funkce použít regulární výraz. Interně se potom bude volat standardní funkce re_find, která vrátí nalezený řetězec, resp. přesněji řečeno řetězec odpovídající regulárnímu výrazu.
Nejprve si vše otestujme na regulárním výrazu, který pouze obsahuje nějakou sekvenci znaků a nikoli znaky se speciálním významem (hvězdičky, tečky, otazníky, závorky všeho druhu atd.):
from funcy import lmap message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() found = lmap("olo", words) print(found)
Výsledkem bude následující seznam (v případě map pak sekvence), obsahující prakticky samé hodnoty None pro slova, která neobsahují sekvenci znaků „olo“ a hodnoty „olo“ pro původní slova „dolor“ a „dolore“:
[None, None, 'olo', None, None, None, None, None, None, None, None, None, None, None, None, None, 'olo', None, None]
Zajímavější situace nastane, pokud bude regulární výraz obsahovat žolíkové znaky atd.:
from funcy import lmap message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() found = lmap(".*olo.*", words) print(found)
Nyní bude možné všechna slova obsahující sekvenci znaků „olo“ ponechat na výstupu, zbylá slova se nahradí za None:
[None, None, 'dolor', None, None, None, None, None, None, None, None, None, None, None, None, None, 'dolore', None, None]
Regulární výrazy ve funkcích map/lmap tedy mají poněkud méně způsobů použití, než ve funkcích filter/lfilter/remove/lremove.
13. Získání unikátních prvků ze vstupní sekvence funkcí distinct
Další užitečnou dvojicí funkcí pro práci se sekvencemi, kterou nalezneme v knihovně funcy, jsou funkce určené pro získání všech unikátních prvků ze vstupní sekvence – tj. výsledkem bude nová sekvence nebo seznam, v němž nebudou duplikátní prvky. Tato dvojice funkcí se jmenuje distinct a ldistinct (tj. jedná se o jméno, které nalezneme i v mnoha funkcionálních programovacích jazycích).
Podívejme se nejdříve na funkci distinct aplikovanou na sekvenci slov, přičemž se všechna slova v této sekvenci opakují nejméně třikrát:
from funcy import distinct message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" message *= 3 print(message) words = message.split() distilled = distinct(words) print(distilled) print(list(distilled))
Tento skript nejprve vypíše upravený vstup (původní zpráva je ještě dvakrát zduplikována) a dále výsledek získaný funkcí distinct:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliquaLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliquaLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua <generator object distinct at 0x7f76aa513510> ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliquaLorem', 'aliqua']
Povšimněte si, že ve výsledné sekvenci se skutečně žádné slovo neopakuje – všechny prvky jsou unikátní.
Funkce ldistinct se od výše popsané funkce distinct odlišuje jen tím, že – jak jste správně uhodli – nevrací sekvenci, ale seznam s unikátními prvky:
from funcy import ldistinct message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" message *= 3 print(message) words = message.split() distilled = ldistinct(words) print(distilled) print(list(distilled))
Výsledky vypsané tímto skriptem ukazují, že se skutečně vrátil přímo seznam (dvě poslední zprávy jsou totožné):
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliquaLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliquaLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliquaLorem', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliquaLorem', 'aliqua']
14. Filtrace prvků sekvence funkcí takewhile
Ze sekvencí (a to i ze sekvencí nekonečných) je možné získat začátek či naopak zbytek sekvence s využitím funkcí nazvaných takewhile a dropwhile. Těmto funkcím je zapotřebí v prvním parametru předat takzvaný predikát určující, zda prvek splňuje nějakou podmínku (ovšem pozor – chování je odlišné od již dříve popsané funkce filter či remove). V případě prvního parametru předávaného do takewhile a dropwhile se jedná o běžnou funkci popř. o funkci anonymní, která by měla akceptovat jeden parametr (hodnotu prvku ze sekvence) a vracet by měla pravdivostní hodnotu True či False popř. None atd., který má v kontextu pravdivostních hodnot stejný význam jako False.
Podívejme se, jak lze získat ty prvky ze začátku sekvence, které jsou menší než 10 (výsledkem může být i prázdná sekvence):
from funcy import takewhile values = range(1000) selected = takewhile(lambda x:x < 10, values) print(selected) print(list(selected))
Výsledek:
<itertools.takewhile object at 0x7f4593b7bd80> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Ve druhém demonstračním příkladu začneme procházet sekvencí slov s tím, že se zastavíme na prvním slovu, které nebude delší než dva znaky:
from funcy import takewhile message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = takewhile(lambda x:len(x) > 2, words) print(filtered) print(list(filtered))
Výsledek:
<itertools.takewhile object at 0x7f4250789dc0> ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'sed']
15. Filtrace prvků sekvence funkcí dropwhile
Vyzkoušejme si nyní funkci vyššího řádu dropwhile. Přeskočíme všechny prvky na začátku sekvence, které jsou ostře menší než 990. Vrátí se zbytek sekvence, v tomto konkrétním případě hodnoty od 990 do 999 (včetně):
from funcy import dropwhile values = range(1000) selected = dropwhile(lambda x:x < 990, values) print(selected) print(list(selected))
Výsledek:
<itertools.dropwhile object at 0x7f9a46e10e00> [990, 991, 992, 993, 994, 995, 996, 997, 998, 999]
V dalším skriptu se přeskočí všechna slova na začátku sekvence, která jsou delší než dva znaky. Jakmile se nalezne kratší slovo, vrátí se celý zbytek sekvence:
from funcy import dropwhile message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = dropwhile(lambda x:len(x) > 2, words) print(filtered) print(list(filtered))
Výsledek:
<itertools.dropwhile object at 0x7f6604fbad00> ['do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua']
16. Rozdělení sekvence na prvky odpovídající predikátu a na prvky, které predikátu neodpovídají
Potenciálně velmi užitečná je funkce nazvaná split. Jedná se o kombinaci funkcí filter a remove – split vrátí dvě hodnoty, přičemž první hodnotou bude sekvence s prvky odpovídajícími predikátu a ve druhé hodnotě budou všechny prvky, které predikátu neodpovídají:
from funcy import split values = range(50) selected, unselected = split(lambda x:x < 25, values) print(list(selected)) print(list(unselected))
Pokud tento skript spustíme, vrátí se dvě sekvence s tímto obsahem:
[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, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
Existuje samozřejmě i alternativní funkce nazvaná lsplit, která vrací dvojici seznamů:
from funcy import lsplit values = range(50) selected, unselected = lsplit(lambda x:x < 25, values) print(selected) print(unselected)
Výsledek získaný po spuštění tohoto skriptu bude stejný, jako v předchozím případě, ovšem chybí zde konstruktor seznamu list:
[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, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
Podobně si můžeme nechat rozdělit sekvenci slov na dlouhá a krátká slova:
from funcy import split message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() selected, unselected = split(lambda x:len(x) > 5, words) print(list(selected)) print(list(unselected))
S výsledkem:
['consectetur', 'adipiscing', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'elit,', 'sed', 'do', 'ut', 'et', 'magna']
A pro úplnost alternativní řešení založené na funkci vyššího řádu lsplit:
from funcy import lsplit message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() selected, unselected = lsplit(lambda x:len(x) > 5, words) print(selected) print(unselected)
Výsledná dvojice seznamů:
['consectetur', 'adipiscing', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'aliqua'] ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'elit,', 'sed', 'do', 'ut', 'et', 'magna']
17. Kombinace funkcí vyššího řádu takewhile a dropwhile
V předchozí kapitole jsme si ukázali, jak snadno se s využitím funkce split daly zkombinovat algoritmy realizované ve funkcích filter a remove. Knihovna funcy jde ovšem ještě dále a nabízí uživatelům možnost kombinace funkcí takewhile a dropwhile, které vlastně mají taktéž „komplementární“ význam. Ona kombinace je realizována ve funkci nazvané split_by, které se předává predikát a vstupní sekvence. Funkce split_by vrátí dvojici sekvencí, přičemž v první výstupní sekvenci budou uloženy prvky získané ze začátku vstupní sekvence, které odpovídají predikátu, a ve druhé výstupní sekvenci zbytek vstupní sekvence (tj. od toho prvku, který jako první neodpovídá predikátu).
Podívejme se, jak to bude vypadat pro vstupní sekvenci s hodnotami 0 až 99 a predikát „x < 10“:
from funcy import split_by values = range(100) selected, rest = split_by(lambda x:x < 10, values) print(list(selected)) print(list(rest))
Výsledkem budou dvě sekvence; rozdělení začalo (podle očekávání) na prvku s hodnotou 10, který již predikátu neodpovídá:
[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, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Podobným způsobem můžeme zpracovat sekvenci slov takovým způsobem, že nalezneme první slovo, které je kratší než dva znaky. Prvky před tímto slovem budou vráceny v první výstupní sekvenci, zbylé prvky pak v sekvenci druhé:
from funcy import split_by message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered, rest = split_by(lambda x:len(x) > 2, words) print(list(filtered)) print(list(rest))
Příslušné výstupní sekvence budou vypadat takto:
['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'sed'] ['do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua']
18. Rozdělení prvků sekvence do většího množství sekvencí pomocí partition_by
Poslední funkcí, s níž se v dnešním článku seznámíme, je funkce nazvaná partition_by. Tato funkce rozdělí vstupní sekvenci do (obecně libovolného) množství výstupních sekvencí na základě výsledku získaného výpočtem realizovaným v předané funkci. Pokud tato funkce vrací stejný výsledek (libovolného typu či hodnoty), budou prvky ukládány do stejné výstupní sekvence. Jakmile se výsledek této funkce změní, je vytvořena nová výstupní sekvence a prvky začnou být ukládány do ní.
Podívejme se nyní na jednoduchý příklad použití této funkce pro rozdělení sekvence celočíselných hodnot 0 až 19 do skupin po pěti prvcích. Povšimněte si, že počítanou hodnotou je x//5, tedy celočíselný podíl hodnoty prvku pěti, což u monotonně rostoucí sekvence bude plně funkční:
from funcy import partition_by values = range(20) sequences = partition_by(lambda x : x // 5, values) for sequence in sequences: print(list(sequence))
Výsledkem bude čtveřice sekvencí resp. přesněji řečeno sekvence se čtyřmi prvky typu sekvence:
[0, 1, 2, 3, 4] [5, 6, 7, 8, 9] [10, 11, 12, 13, 14] [15, 16, 17, 18, 19]
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
- 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/