Odkryj potęgę modułu Itertools w Pythonie
Zgodnie z dokumentacją języka Python, moduł Itertools to zbiór wysoce wydajnych i oszczędzających zasoby narzędzi przeznaczonych do operacji na iteratorach. Te wszechstronne funkcje mogą być wykorzystywane indywidualnie lub w połączeniu, umożliwiając zgrabne i efektywne tworzenie oraz manipulowanie iteratorami, minimalizując przy tym zużycie pamięci.
Moduł Itertools to kopalnia wiedzy dla programistów pracujących z iteratorami, zwłaszcza podczas przetwarzania dużych zbiorów danych. Jego funkcje pozwalają na przekształcanie istniejących iteratorów w bardziej złożone struktury.
Wykorzystanie Itertools przyczynia się do redukcji błędów w kodzie, podnosząc jego jakość, czytelność i łatwość konserwacji.
Biorąc pod uwagę funkcjonalności oferowane przez moduł Itertools, iteratory można podzielić na kilka kategorii:
# 1. Iteratory nieskończone
Te iteratory otwierają drzwi do pracy z niekończącymi się sekwencjami, prowadząc do pętli o nieograniczonym czasie trwania, chyba że zaimplementuje się warunek ich przerwania. Są one przydatne w symulacjach i generowaniu nieskończonych ciągów. Itertools oferuje trzy podstawowe iteratory nieskończone: `count()`, `cycle()` i `repeat()`.
#2. Iteratory kombinatoryczne
Iteratory kombinatoryczne zawierają funkcje niezbędne przy obliczeniach iloczynów kartezjańskich, generowaniu kombinacji i permutacji elementów w zbiorach iterowalnych. Stanowią one kluczowe narzędzie podczas eksploracji wszystkich możliwych układów lub połączeń elementów. W module Itertools znajdują się cztery iteratory tego typu: `product()`, `permutations()`, `combinations()` i `combinations_with_replacement()`.
#3. Iteratory kończące się na najkrótszej sekwencji wejściowej
Są to iteratory, które działają na skończonych sekwencjach, generując wynik w zależności od zastosowanej funkcji. Do tej grupy zaliczamy iteratory takie jak: `accumulate()`, `chain()`, `chain.from_iterable()`, `compress()`, `dropwhile()`, `filterfalse()`, `groupby()`, `islice()`, `pairwise()`, `starmap()`, `takewhile()`, `tee()` oraz `zip_longest()`.
Przyjrzyjmy się bliżej działaniu poszczególnych funkcji z modułu Itertools, z podziałem na wspomniane kategorie:
Iteratory nieskończone
W skład tej grupy wchodzą trzy podstawowe iteratory:
# 1. `count()`
Funkcja `count(start, step)` generuje nieskończony ciąg liczb, rozpoczynając od podanej wartości początkowej. Akceptuje dwa opcjonalne argumenty: `start` i `step`. Argument `start` definiuje początek sekwencji, a jeśli nie zostanie podany, domyślnie wynosi 0. Argument `step` określa różnicę między kolejnymi liczbami; domyślnie wynosi 1.
import itertools # Zliczanie od 4, krokiem 2 for i in itertools.count(4, 2): # Warunek zakończenia pętli aby uniknąć nieskończonego zapętlenia if i == 14: break else: print(i) # Wyjście: 4, 6, 8, 10, 12
Wyjście:
4 6 8 10 12
#2. `cycle()`
Funkcja `cycle(iterable)` przyjmuje jako argument obiekt iterowalny, a następnie przechodzi cyklicznie przez jego elementy, umożliwiając dostęp do nich w ich pierwotnej kolejności.
Na przykład, przekazując do `cycle()` listę `[„czerwony”, „zielony”, „żółty”]`, w pierwszym cyklu uzyskamy dostęp do „czerwonego”, w drugim do „zielonego”, a następnie „żółtego”. W czwartym cyklu, gdy wszystkie elementy zostaną wyczerpane, cykl rozpocznie się od „czerwonego” i będzie kontynuowany w nieskończoność.
Wynik wywołania `cycle()` jest przechowywany w zmiennej w celu utworzenia iteratora, który zachowuje swój stan, unikając resetowania cyklu i dostępu wyłącznie do pierwszego elementu.
import itertools kolory = ["czerwony", "zielony", "żółty"] # Przekazanie kolorów do cycle() cykl_kolorow = itertools.cycle(kolory) print(cykl_kolorow) # Range aby zatrzymać nieskończoną pętlę po 7 wydrukach # next() zwraca następny element z iteratora for i in range(7): print(next(cykl_kolorow))
Wyjście:
czerwony zielony żółty czerwony zielony żółty czerwony
#3. `repeat()`
Funkcja `repeat(elem, n)` przyjmuje dwa argumenty: element do powtórzenia (`elem`) oraz liczbę jego powtórzeń (`n`). Elementem może być pojedyncza wartość lub obiekt iterowalny. Brak `n` spowoduje, że element będzie powtarzany w nieskończoność.
import itertools for i in itertools.repeat(10, 3): print(i)
Wyjście:
10 10 10
Iteratory kombinatoryczne
W skład tej grupy wchodzą:
# 1. `product()`
Funkcja `product()` służy do obliczania iloczynu kartezjańskiego zbiorów iterowalnych. Dla dwóch zbiorów `x = {7,8}` i `y = {1,2,3}`, iloczyn kartezjański x i y to zbiór wszystkich możliwych kombinacji, gdzie pierwszy element pochodzi z x, a drugi z y, czyli `[(7, 1), (7, 2), (7, 3), (8, 1), (8, 2), (8, 3)]`.
`product()` akceptuje opcjonalny parametr `repeat`, który pozwala obliczyć iloczyn kartezjański zbioru iterowalnego ze sobą samym. `repeat` określa liczbę powtórzeń każdego elementu z iteracji wejściowych.
Na przykład, `product(’ABCD’, repeat=2)` da w rezultacie kombinacje takie jak `(’A’, 'A’), (’A’, 'B’), (’A’, 'C’)`, itd. Jeśli `repeat` zostanie ustawione na 3, funkcja zwróci kombinacje typu `(’A’, 'A’, 'A’), (’A’, 'A’, 'B’), (’A’, 'A’, 'C’)`, itd.
from itertools import product # product() z opcjonalnym argumentem repeat print("product() z opcjonalnym argumentem repeat") print(list(product('ABC', repeat = 2))) # product bez repeat print("product() BEZ opcjonalnego argumentu repeat") print(list(product([7,8], [1,2,3])))
Wyjście:
product() z opcjonalnym argumentem repeat [('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')] product() BEZ opcjonalnego argumentu repeat [(7, 1), (7, 2), (7, 3), (8, 1), (8, 2), (8, 3)]
#2. `permutations()`
Funkcja `permutations(iterable, group_size)` zwraca wszystkie możliwe permutacje dla zadanego obiektu iterowalnego. Permutacja określa liczbę sposobów, na jakie można ustawić elementy w zbiorze. `permutations()` przyjmuje opcjonalny argument `group_size`. Jeśli nie zostanie on podany, wygenerowane permutacje będą miały długość równą długości obiektu iterowalnego.
import itertools liczby = [1, 2, 3] permutacje_rozmiar = list(itertools.permutations(liczby,2)) permutacje_bez_rozmiar = list(itertools.permutations(liczby)) print("Permutacje o rozmiarze 2") print(permutacje_rozmiar) print("Permutacje BEZ argumentu rozmiaru") print(permutacje_bez_rozmiar)
Wyjście:
Permutacje o rozmiarze 2 [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)] Permutacje BEZ argumentu rozmiaru [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
#3. `combinations()`
Funkcja `combinations(iterable, size)` zwraca wszystkie możliwe kombinacje elementów o określonej długości ze zbioru iterowalnego. Argument `size` definiuje rozmiar każdej kombinacji.
Wyniki są uporządkowane. Kombinacja różni się od permutacji, gdzie kolejność elementów jest istotna. W kombinacji kolejność nie ma znaczenia, np. w zbiorze `[A, B, C]` istnieje 6 permutacji: `AB, AC, BA, BC, CA, CB`, ale tylko 3 kombinacje: `AB, AC, BC`.
import itertools liczby = [1, 2, 3,4] kombinacja_rozmiar2 = list(itertools.combinations(liczby,2)) kombinacja_rozmiar3 = list(itertools.combinations(liczby, 3)) print("Kombinacje o rozmiarze 2") print(kombinacja_rozmiar2) print("Kombinacje o rozmiarze 3") print(kombinacja_rozmiar3)
Wyjście:
Kombinacje o rozmiarze 2 [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)] Kombinacje o rozmiarze 3 [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
#4. `combinations_with_replacement()`
Funkcja `combinations_with_replacement(iterable, size)` tworzy wszystkie możliwe kombinacje o danej długości, z uwzględnieniem powtarzających się elementów. Argument `size` określa rozmiar generowanych kombinacji.
W odróżnieniu od `combinations()`, ta funkcja pozwala na generowanie kombinacji z powtarzającymi się elementami, np. `(1,1)`, czego nie można osiągnąć przy pomocy zwykłych kombinacji.
import itertools liczby = [1, 2, 3,4] kombinacja_rozmiar2 = list(itertools.combinations_with_replacement(liczby,2)) print("combinations_with_replacement => rozmiar 2") print(kombinacja_rozmiar2)
Wyjście:
combinations_with_replacement => rozmiar 2 [(1, 1), (1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (4, 4)]
Iteratory kończące się
Ta kategoria obejmuje między innymi iteratory takie jak:
# 1. `accumulate()`
Funkcja `accumulate(iterable, funkcja)` przyjmuje obiekt iterowalny i opcjonalnie funkcję. Zwraca ona skumulowany wynik zastosowania funkcji na elementach iterowalnych. W przypadku braku funkcji, wykonywane jest sumowanie i zwracane są wyniki skumulowane.
import itertools import operator liczby = [1, 2, 3, 4, 5] # Akumulowanie sumy liczb akumulowana_wartosc = itertools.accumulate(liczby) akumulowana_mul = itertools.accumulate(liczby, operator.mul) print("Akumulacja bez funkcji") print(list(akumulowana_wartosc)) print("Akumulacja z mnożeniem") print(list(akumulowana_mul))
Wyjście:
Akumulacja bez funkcji [1, 3, 6, 10, 15] Akumulacja z mnożeniem [1, 2, 6, 24, 120]
#2. `chain()`
Funkcja `chain(iterable_1, iterable_2, …)` łączy wiele obiektów iterowalnych w jeden, tworząc iterowalny zawierający wszystkie wartości z przekazanych obiektów.
import itertools litery = ['A', 'B', 'C', 'D'] liczby = [1, 2, 3] kolory = ['czerwony', 'zielony', 'żółty'] # Łączenie liter i liczb polaczony_iterowalny = list(itertools.chain(litery, liczby, kolory)) print(polaczony_iterowalny)
Wyjście:
['A', 'B', 'C', 'D', 1, 2, 3, 'czerwony', 'zielony', 'żółty']
#3. `chain.from_iterable()`
Funkcja `chain.from_iterable(iterable)` jest podobna do `chain()`, ale akceptuje tylko jeden obiekt iterowalny zawierający pod-iterowalne, które są następnie łączone.
import itertools litery = ['A', 'B', 'C', 'D'] liczby = [1, 2, 3] kolory = ['czerwony', 'zielony', 'żółty'] iterowalny = ['hello',kolory, litery, liczby] chain = list(itertools.chain.from_iterable(iterowalny)) print(chain)
Wyjście:
['h', 'e', 'l', 'l', 'o', 'czerwony', 'zielony', 'żółty', 'A', 'B', 'C', 'D', 1, 2, 3]
#4. `compress()`
Funkcja `compress(data, selectors)` przyjmuje dane (obiekt iterowalny) oraz selektory (obiekt iterowalny zawierający wartości boolowskie True/False lub alternatywnie 1/0). Funkcja filtruje dane na podstawie wartości przekazanych w selektorach.
Elementy z danych, odpowiadające wartości True lub 1 w selektorze, są wybierane, pozostałe elementy są ignorowane. Jeśli liczba elementów w selektorze jest mniejsza niż w danych, elementy danych, które nie mają przypisanych wartości boolowskich, zostaną pominięte.
import itertools # data ma 10 elementów data = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'] # przekazanie 9 elementów selektora selektory = [True, False, 1, False, 0, 1, True, False, 1] # Wybieranie elementów na podstawie selektorów przefiltrowane_dane = list(itertools.compress(data, selektory)) print(przefiltrowane_dane)
Wyjście:
['A', 'C', 'F', 'G', 'I']
#5. `dropwhile()`
Funkcja `dropwhile(funkcja, sekwencja)` przyjmuje funkcję z warunkiem zwracającym wartość logiczną oraz sekwencję danych. Pomija elementy sekwencji do momentu, gdy warunek przestanie być spełniony, a następnie włącza do wyniku pozostałe elementy, bez względu na to, czy spełniają warunek.
import itertools liczby = [1, 2, 3, 4, 5, 1, 6, 7, 2, 1, 8, 9, 0, 7] # Pomija elementy dopóki warunek jest prawdziwy przefiltrowane_liczby = list(itertools.dropwhile(lambda x: x < 5, liczby)) print(przefiltrowane_liczby)
Wyjście:
[5, 1, 6, 7, 2, 1, 8, 9, 0, 7]
#6. `filterfalse()`
Funkcja `filterfalse(funkcja, sekwencja)` przyjmuje funkcję z warunkiem oraz sekwencję danych. Zwraca te elementy sekwencji, które nie spełniają warunku w funkcji.
import itertools liczby = [1, 2, 3, 4, 2, 3, 5, 6, 5, 8, 1, 2, 3, 6, 2, 7, 4, 3] # Filtruje elementy dla których warunek jest fałszywy przefiltrowane_liczby = list(itertools.filterfalse(lambda x: x < 4, liczby)) print(przefiltrowane_liczby)
Wyjście:
[4, 5, 6, 5, 8, 6, 7, 4]
#7. `groupby()`
Funkcja `groupby(iterable, key)` tworzy iterator, który zwraca kolejne klucze i grupy z posortowanego obiektu iterowalnego. Przekazany obiekt iterowalny musi być posortowany wg tej samej funkcji klucza. Funkcja klucza określa wartość klucza dla każdego elementu w iterowalnym obiekcie.
import itertools lista_wejsciowa = [("D", "K"), ("D", "P"), ("D", "C"),("W", "L"), ("W", "Z"), ("W", "E")] klasyfikacja = itertools.groupby(lista_wejsciowa,lambda x: x[0]) for klucz, wartosc in klasyfikacja: print(klucz,":",list(wartosc))
Wyjście:
D : [('D', 'K'), ('D', 'P'), ('D', 'C')] W : [('W', 'L'), ('W', 'Z'), ('W', 'E')]
#8. `islice()`
Funkcja `islice(iterable, start, stop, step)` umożliwia wycinanie fragmentów z obiektu iterowalnego za pomocą przekazanych wartości: start, stop i opcjonalnie step. Numeracja elementów rozpoczyna się od 0, a element na pozycji `stop` nie jest włączony do wyniku.
import itertools liczby = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] # Wybieranie elementów w danym zakresie wybrane_liczby = list(itertools.islice(liczby, 2, 10)) wybrane_liczby_krok = list(itertools.islice(liczby, 2, 10,2)) print("islice bez ustawiania kroku") print(wybrane_liczby) print("islice z krokiem 2") print(wybrane_liczby_krok)
Wyjście:
islice bez ustawiania kroku [3, 4, 5, 6, 7, 8, 9, 10] islice z krokiem 2 [3, 5, 7, 9]
#9. `pairwise()`
Funkcja `pairwise(iterable)` zwraca kolejne, nakładające się pary elementów z przekazanego obiektu iterowalnego, w kolejności ich występowania. Jeśli obiekt iterowalny ma mniej niż dwa elementy, wynik funkcji `pairwise()` będzie pusty.
from itertools import pairwise liczby = [1, 2, 3, 4, 5, 6, 7, 8] slowo = 'SWIAT' pojedyncze = ['A'] print(list(pairwise(liczby))) print(list(pairwise(slowo))) print(list(pairwise(pojedyncze)))
Wyjście:
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8)] [('S', 'W'), ('W', 'I'), ('I', 'A'), ('A', 'T')] []
#10. `starmap()`
Funkcja `starmap(function, iterable)` jest alternatywą dla `map()`, gdy argumenty są już zgrupowane w krotki. Funkcja `starmap()` stosuje funkcję do elementów przekazanego obiektu iterowalnego, gdzie elementy te są krotkami.
import itertools iter_starmap = [(123, 63, 13), (5, 6, 52), (824, 51, 9), (26, 24, 16), (14, 15, 11)] print (list(itertools.starmap(min, iter_starmap)))
Wyjście:
[13, 5, 9, 16, 11]
#11. `takewhile()`
Funkcja `takewhile(funkcja, iterowalny)` działa odwrotnie do `dropwhile()`. Przyjmuje funkcję z warunkiem oraz iterację. Uwzględnia elementy z obiektu iterowalnego, które spełniają warunek, do momentu gdy warunek przestanie być spełniony. Po tym momencie wszystkie następne elementy są ignorowane.
import itertools liczby = [1, 2, 3, 4, 5, 1, 6, 7, 2, 1, 8, 9, 0, 7] # Zawiera elementy dopóki warunek jest prawdziwy przefiltrowane_liczby = list(itertools.takewhile(lambda x: x < 5, liczby)) print(przefiltrowane_liczby)
Wyjście:
[1, 2, 3, 4]
#12. `tee()`
Funkcja `tee(iterable, n)` tworzy `n` niezależnych iteratorów z jednego obiektu iterowalnego. Domyślnie `n` wynosi 2.
import itertools liczby = [1, 2, 3, 4, 5] # Tworzy 2 niezależne iteratory iter1, iter2 = itertools.tee(liczby, 2) print(list(iter1)) print(list(iter2))
Wyjście:
[1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
#13. `zip_longest()`
Funkcja `zip_longest(iterables, fillvalue)` przyjmuje wiele obiektów iterowalnych i opcjonalną wartość wypełnienia. Zwraca iterator, który łączy elementy z przekazanych iteratorów. Jeśli iteratory nie są równej długości, brakujące elementy są zastępowane podaną wartością wypełnienia, aż do wyczerpania najdłuższego iteratora.
import itertools imiona = ['Jan', 'Mateusz', 'Maria', 'Alicja', 'Bob', 'Karol', 'Furia'] wieki = [25, 30, 12, 13, 42] # Łączenie imion i wieków, brakujące wieki wypełnione "-" polaczone = itertools.zip_longest(imiona, wieki, fillvalue="-") for imie, wiek in polaczone: print(imie, wiek)
Wyjście:
Jan 25 Mateusz 30 Maria 12 Alicja 13 Bob 42 Karol - Furia -
Podsumowanie
Moduł Itertools jest niezbędnym narzędziem w arsenale każdego programisty Pythona. Szerokie zastosowanie znajduje w programowaniu funkcyjnym, przetwarzaniu i transformacji danych, filtracji, grupowaniu, łączeniu iteracji, kombinatoryce, a także pracy z nieskończonymi sekwencjami.
Jako programista Pythona, wiele zyskasz, zapoznając się z funkcjonalnościami Itertools. Ten artykuł stanowi doskonały punkt wyjścia do eksploracji potęgi tego modułu.
newsblog.pl
Maciej – redaktor, pasjonat technologii i samozwańczy pogromca błędów w systemie Windows. Zna Linuxa lepiej niż własną lodówkę, a kawa to jego główne źródło zasilania. Pisze, testuje, naprawia – i czasem nawet wyłącza i włącza ponownie. W wolnych chwilach udaje, że odpoczywa, ale i tak kończy z laptopem na kolanach.