{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Elementy programowania funkcyjnego\n", "\n", "## Iteratory\n", "\n", "Pętla ``for`` pozwala w Pythonie na *iterowanie* po elementach jakiejkolwiek sekwencji i wykonanie pewnych operacji dla każdego jej elementu.\n", "\n", "### Iteracja po liście\n", "\n", "Iterować można po liście:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 4 5 10 " ] } ], "source": [ "for x in [1,4,5,10]:\n", " print(x, end=' ')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Iteracja po słowniku\n", "\n", "Iterując po słowniku, uzyskujemy dostęp do jego kluczy:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "GOOG\n", "AAPL\n", "YHOO\n" ] } ], "source": [ "prices = { 'GOOG' : 490.10,\n", " 'AAPL' : 145.23,\n", " 'YHOO' : 21.71 \n", "}\n", "\n", "for key in prices:\n", " print(key)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Iteracja po stringu\n", "\n", "*String* (napis) można traktować jako listę znaków.\n", "Iterując po napisie, uzyskujemy dostęp do poszczególnych znaków:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Y\n", "o\n", "w\n", "!\n" ] } ], "source": [ "text = \"Yow!\"\n", "\n", "for character in text:\n", " print(character)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Iteracja po pliku\n", "\n", "Iterować można nie tylko po kolekcjach, ale także obiektach, które w jakiś sposób reprezentują zbiór obiektów.\n", "Na przykład, plik można traktować jako zbiór linii.\n", "W wyniku iteracji po pliku otrzymujemy linie (razem ze znakiem końca wiersza):" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Real Programmers write in FORTRAN\n", "Maybe they do now,\n", "in this decadent era of\n", "Lite beer, hand calculators, and \"user-friendly\" software\n", "but back in the Good Old Days,\n", "when the term \"software\" sounded funny\n", "and Real Computers were made out of drums and vacuum tubes,\n", "Real Programmers wrote in machine code.\n", "Not FORTRAN. Not RATFOR. Not, even, assembly language.\n", "Machine Code.\n", "Raw, unadorned, inscrutable hexadecimal numbers.\n", "Directly." ] } ], "source": [ "for line in open(\"real.txt\"):\n", " print(line, end='')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Protokół iteracji\n", "\n", "Możliwość iterowania po różnych obiektach wynika z istnienia ścisłego protokołu.\n", "Iterować można po każdym obiekcie, który spełnia ten protokół.\n", "W szczególności, instancje Twoich własnych klas również mogą być iterowalne." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "items = [1, 4, 5]\n", "\n", "iterator = iter(items)\n", "next(iterator)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0miterator\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "next(iterator)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Wbudowana funkcja ``iter(x)`` wywołuje ``x.__iter__()``.\n", "\n", "Z kolei ``next(x)`` deleguje do ``x.__next__()`` pod Pythonem 3 lub do ``x.next()`` w przypadku Pythona 2." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0miterator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__next__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0miterator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__next__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0miterator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__next__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "items = [1, 4, 5]\n", "iterator = items.__iter__()\n", "iterator.__next__() == 1\n", "iterator.__next__()\n", "iterator.__next__() == 5\n", "iterator.__next__()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Protokół składa się z dwóch metod:\n", "\n", "- Obiekt, który ma być iterowalny, musi mieć metodę `__iter__()`, która powinna zwrócić *iterator*.\n", "\n", "- Iterator powinien mieć metodę `__next__()` (lub `next()` w Pythonie 2) zwracającą przy kolejnych wywołaniach kolejne elementy.\n", " Jeżeli wszystkie elementy zostały już zwrócone, powinien zostać zgłoszony wyjątek `StopIteration`.\n", "\n", "Iterator może być tym samym obiektem, co iterowany obiekt.\n", "\n", "W takiej sytuacji implementacja metody `__iter__()` sprowadza się do zwrócenia tego obiektu:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "class Foo:\n", " def __iter__(self):\n", " return self\n", " \n", " def __next__(self):\n", " \"\"\"get next element\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Należy jednak pamiętać, że po obiekcie takiej klasy można iterować tylko raz (tak jak w przypadku wyrażeń generatorowych).\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Iteracja po własnych typach\n", "\n", "Poniżej zostanie przedstawiona implementacja klasy ``Countdown`` umożliwiającej odliczenia w dół.\n", "\n", "Przykład użycia takiej klasy:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for i in Countdown(10):\n", " print(i, end=' ')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Implementacja wykorzystuje trik przestawiony wcześniej, to znaczy metoda `__iter__()` zwraca ten sam obiekt.\n", "Dzięki temu iterator jest tym samym obiektem, po którym iterujemy.\n", "W konsekwencji, nie ma potrzeby pisania dwóch osobnych klas." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "class Countdown:\n", " def __init__(self,start):\n", " self.count = start\n", " \n", " def __iter__(self):\n", " return self\n", " \n", " def __next__(self):\n", " if self.count <= 0:\n", " raise StopIteration\n", " r = self.count\n", " self.count -= 1\n", " return r" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10 9 8 7 6 5 4 3 2 1 " ] } ], "source": [ "for i in Countdown(10):\n", " print(i, end=' ')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Wbudowane funkcje używające obiektów iterowalnych\n", "\n", "Python posiada wbudowane funkcje, to znaczy takie, których nie trzeba importować.\n", "Niektóre z nich operują na dowolnych obiektach iterowalnych, w szczególności na kolekcjach.\n", "\n", "Funkcje ``sum``, ``min`` i ``max`` agregują przekazaną kolekcję i zwracają jedną wartość (odpowiednio sumę elementów, najmniejszy i największy element).\n", "Dwie ostatnie funkcje generują ``ValueError``, jeżeli przekazana kolekcja jest pusta.\n", "\n", "Funkcje ``list``, ``tuple``, ``set`` i ``dict`` służą do stworzenia nowej kolekcji danego typu.\n", "Jeżeli nie zostanie podany żaden element, zwrócona zostanie pusta kolekcja (nie zawierająca żadnego elementu).\n", "Jednak najczęściej podaje się jeden argument (dowolny iterowalny obiekt).\n", "\n", "Często dysponujemy *generatorami*, to znaczy obiektami przypominającymi kolekcje, ale wyliczającymi elementy na żądanie.\n", "Generatory zostaną szczegółowo omówione w następnym rozdziale.\n", "Generatory są zwracane na przykład przez funkcje ``filter``, ``map`` i ``zip``.\n", "Jeżeli chcemy wyświetlić elementy takiego generatora, możemy \"przekonwertować\" go na listę przy użyciu funkcji ``list``:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(1, 'a'), (2, 'b'), (3, 'c')]" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = [1, 2, 3]\n", "b = ['a', 'b', 'c']\n", "ab_zipped = zip(a, b)\n", "list(ab_zipped)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Generatory\n", "\n", "Generator jest funkcją, która zwraca sekwencję wyników zamiast pojedynczej wartości.\n", "Wewnątrz generatora używana jest instrukcja ``yield`` zamiast ``return``.\n", "Służy ona do zwracania kolejnych wartości." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "def countdown(n):\n", " while n > 0:\n", " yield n\n", " n -= 1" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5 4 3 2 1 " ] } ], "source": [ "for i in countdown(5):\n", " print(i, end=' ')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Wywołanie funkcji generatora tworzy obiekt generatora, ale nie rozpoczyna działania tej funkcji.\n", "Przy pierwszym wywołaniu metody ``__next__()`` następuje wykonanie funkcji generatora aż do napotkania instrukcji ``yield``.\n", "Wtedy wykonywanie funkcji zostaje wstrzymane, a wartość zwrócona.\n", "Przy kolejnych wywołaniach metody ``__next__()`` następuje wznowienie generatora z miejsca, w którym został on poprzednio wstrzymany.\n" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "def countdown(n):\n", " print('start countdown')\n", " while n > 0:\n", " print('before yield')\n", " yield n\n", " print('after yield')\n", " n -= 1" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "it = countdown(3)\n", "it" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "start countdown\n", "before yield\n" ] }, { "data": { "text/plain": [ "3" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(it)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "after yield\n", "before yield\n" ] }, { "data": { "text/plain": [ "2" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(it)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "after yield\n", "before yield\n" ] }, { "data": { "text/plain": [ "1" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(it)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "after yield\n" ] }, { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "next(it)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### yield from\n", "\n", "Jeżeli chcemy na raz zwrócić więcej niż jedną wartość, można użyć instrukcji ``yield from``.\n", "\n", "Jest ona szczególnie przydatna, jeżeli chcemy zwrócić rezultat innego generatora.\n" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "def flatten_gen():\n", " yield from ['A', 'B']\n", " yield from 'CDE'\n", " yield from range(1, 4)" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['A', 'B', 'C', 'D', 'E', 1, 2, 3]" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(flatten_gen())" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Generatory a iteratory\n", "\n", "Funkcja generatorowa (lub po prostu generator) różni się od obiektu, który wspiera iterację.\n", "\n", "Generator jest operacją jednorazową.\n", "Można iterować po generowanych danych tylko raz.\n", "Ponowna iteracja wymaga wywołania funkcji generatorowej." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Wyrażenia generatorowe\n", "\n", "### Wprowadzenie\n", "\n", "Wyrażenie generatorowe to generatorowa wersja wyrażenia listowego.\n", "Wyrażenie generatorowe zwraca generator, który wylicza kolejne elementy na żądanie." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 4, 9, 16, 25]" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers = [1, 2, 3, 4, 5]\n", "squares = [n * n for n in numbers]\n", "squares" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Zamiast tworzyć listę `numbers` i zużywać pamięć można użyć wyrażenia generatorowego:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ " at 0x7fc079109190>" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "squares_generator = (n*n for n in numbers)\n", "squares_generator" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 4 9 16 25 " ] } ], "source": [ "for s in squares_generator:\n", " print(s, end=' ')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Wyrażenia generatorowe przydają się przy pracy na dużej ilości danych (np. z dużymi plikami).\n", "Jeżeli nie jest możliwe załadowanie wszystkich danych do pamięci, wówczas nie możemy ich przechowywać w liście.\n", "Zamiast tego, można użyć wyrażeń generatorowych.\n", "\n", "Z drugiej strony, generatory są mniej wygodne, ponieważ można iterować po nich tylko raz." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Składnia\n", "\n", "Podobnie jak w przypadku wyrażeń listowych czy słownikowych, możliwe jest kilkukrotne, \"zagnieżdżone\" iterowanie.\n", "Typowym przykładem jest macierz, którą w Pythonie reprezentujemy jako listę list.\n", "Wymaga to najpierw iterowania po macierzy, aby uzyskać dostęp do wewnętrznych list reprezentujących poszczególne wiersze lub kolumny, a następnie po poszczególnych wierszach/kolumnach.\n", "\n", "Składnia wyrażeń generatorowych jest następująca:\n", "\n", "```python\n", "(expression for i in s if cond1\n", " for j in t if cond2\n", " ...\n", " if condfinal)\n", "```\n", "\n", "Powyższy kod jest równoważny:\n", "\n", "```python\n", "for i in s:\n", " if cond1:\n", " for j in t:\n", " if cond2:\n", " ...\n", " if condfinal:\n", " yield expression\n", "```\n", "\n", "Co ciekawe, nawiasy można pominąć, jeżeli wyrażenie generatorowe jest jedynym argumentem funkcji:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "55" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum((n * n for n in numbers))" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "55" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(n * n for n in numbers)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Funkcje `filter` i `map`\n", "\n", "Przy użyciu funkcji ``filter`` i ``map`` można wykonać te same operacje, co z użyciem wyrażeń generatorowych.\n", "Bardzo często korzysta się wówczas z wyrażenia lambda, pozwalającego na zwięzłe stworzenie anonimowej funkcji:" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 4, 5, 42, 5, 3]" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers = [1, -3, 4, 5, 42, -665, 5, 3, -7]\n", "positive_numbers = filter(lambda x: x > 0, numbers)\n", "list(positive_numbers)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "W Pythonie 3 obie funkcje zwracają generator.\n", "Jest to inne zachowanie niż w Pythonie 2, gdzie zwracana jest lista.\n", "\n", "Ze względu na wydajność warto zastąpić ``filter`` i ``map`` wyrażeniami generatorowymi. Unikamy narzutu związanego z wielokrotnym wywoływaniem funkcji.\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Moduł ``itertools``\n", "\n", "Python posiada wiele wbudowanych funkcji zwracających iteratory, na przykład ``zip``, ``map`` lub ``filter``.\n", "Jest wiele innych przydatnych funkcji, które są dostępne w module ``itertools`` stanowiącym część standardowej biblioteki.\n", "Poniżej zostały omówione najważniejsze z nich.\n", "\n", "### ``count``\n", "\n", "``count`` jest jak ``range``, ale zwraca nieskończony iterator (nie podajemy końcowego indeksu).\n", "Podajemy jedynie pierwszy zwracany element (domyślnie zero) oraz krok (domyślnie jeden).\n", "Jeżeli nie podamy żadnych argumentów, dostaniemy iterator zwracający kolejne liczby naturalne od zera.\n", "Poniżej przedstawiono prosty kalkulator działający w nieskończonej pętli:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sum of 45 consecutive integers starting from 1 is greater than 1000\n" ] } ], "source": [ "import itertools\n", "\n", "limit = 1000\n", "\n", "def find_nth(limit):\n", " total = 0\n", " for iter in itertools.count(1):\n", " total += iter\n", " if total > limit:\n", " return iter\n", "\n", "n = find_nth(limit)\n", "print(f\"Sum of {n} consecutive integers starting from 1 is greater than {limit}\")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "O ile ``range`` działa tylko na liczbach całkowitych, to w przypadku ``count`` krok może być liczbą zmiennoprzecinkową." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### ``chain``\n", "\n", "Przy użyciu operatora ``+`` można połączyć (dokonać konkatenacji) dwie listy:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 2, 3, 4, 5, 6, 7]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = [1, 2, 3, 4]\n", "b = [5, 6, 7]\n", "\n", "a + b" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "W przypadku dwóch iteratorów, nie możemy użyć operatora ``+``.\n", "Zamiast tego, należy użyć funkcji ``chain``:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 2, 3, 4, 5, 6, 7]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = range(1, 5)\n", "b = range(5, 8)\n", "\n", "ab_chained = itertools.chain(a, b)\n", "list(ab_chained)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### ``groupby``\n", "\n", "``groupby`` wykonuje tę samą operację, co ``GROUP BY`` z SQL'a.\n", "Przyjmuje listę elementów, a następnie łączy te same elementy w grupy.\n", "\n", "Lista lub iterator przekazany jako argument musi być posortowany rosnąco." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 - [1, 1, 1]\n", "2 - [2, 2, 2]\n", "3 - [3, 3, 3, 3, 3]\n", "4 - [4]\n" ] } ], "source": [ "data = [1, 3, 2, 1, 2, 2, 4, 3, 3, 3, 3, 1]\n", "\n", "data = sorted(data)\n", "\n", "for element, iter in itertools.groupby(data):\n", " print(f\"{element} - {list(iter)}\")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Podobnie jak w przypadku funkcji ``sort``, możemy zdefiniować klucz, według którego elementy będą grupowane.\n", "``groupby`` zwraca iterator par.\n", "Pierwszy element z tej pary to wspólny klucz, natomiast drugi to iterator zwracający wszystkie elementy z danej grupy." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3 -> ['rat', 'bat']\n", "4 -> ['duck', 'bear', 'lion']\n", "5 -> ['eagle', 'shark']\n", "7 -> ['giraffe', 'dolphin']\n" ] } ], "source": [ "animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear',\n", " 'bat', 'dolphin', 'shark', 'lion']\n", "\n", "animals.sort(key=len)\n", "\n", "for length, group in itertools.groupby(animals, len):\n", " print(length, '->', list(group))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### `takewhile`\n", "\n", "``takewhile`` działa podobnie do ``filter``.\n", "Przyjmuje dwa argumenty: funkcję zwracającą ``True`` lub ``False`` dla każdego elementu kolekcji oraz kolekcję.\n", "``filter`` poszukuje wszystkich elementów, dla których funkcja zwraca ``True``,\n", "natomiast ``takewhile`` przerywa przeszukiwanie po natrafieniu na pierwszy element, dla którego funkcja zwróciła ``False``.\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "squares = (x * x for x in itertools.count(0)) # infinite generator of squares\n", "\n", "list(itertools.takewhile(lambda x: x <= 100, squares))" ] } ], "metadata": { "kernelspec": { "display_name": "jb", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }