Czym są funkcje Pythona Itertools?

Photo of author

By maciekx

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