Zadanie
Dopisz kod zwracający piąty element z zadanej kolekcji.
lista = [1, 2, 3, 4, 5] def funkcja(kolekcja): """ napisz kod zwracajacy piąty element kolekcji """ piaty_element = funkcja(x * 2 for x in lista) print(f'piaty element to {piaty_element}')
Podejście pierwsze
Na pierwszy rzut oka problem wydaje się banalny. Czemu by nie wyświetlić po prostu piątego elementu w taki sposób:
lista = [1, 2, 3, 4, 5] def funkcja(kolekcja): return kolekcja[4] piaty_element = funkcja(x * 2 for x in lista) print(f'piaty element to {piaty_element}')
Output:
Python 3.7.4 (default, Jul 9 2019, 00:06:43) [GCC 6.3.0 20170516] on linux Traceback (most recent call last): File "main.py", line 6, in <module> piaty_element = funkcja(x * 2 for x in lista) File "main.py", line 4, in funkcja return kolekcja[4] TypeError: 'generator' object is not subscriptable
No coś nam tu nie poszło. Ma to jednak swoje plusy, bowiem wiemy już z jaką kolekcją mamy do czynienia. Jest to generator. Na kolekcji typu generator nie da się odwołać bezpośrednio do jej piątego elementu, bo ten jeszcze nie istnieje. Generator za każdym razem gdy jest wywoływany zwraca nam kolejną wartość, tworząc ją na bieżąco. Uzbrojeni w tą wiedzę spróbujmy jeszcze raz.
Podejście drugie
Aby dostawać kolejne wartości z naszego generatora wystarczy wywołać na jego rzecz funkcję next
. Wykorzystamy to aby dobrać się do piątego elementu:
lista = [1, 2, 3, 4, 5] def funkcja(kolekcja): next(kolekcja) next(kolekcja) next(kolekcja) next(kolekcja) return next(kolekcja) piaty_element = funkcja(x * 2 for x in lista) print(f'piaty element to {piaty_element}')
Output:
piaty element to 10
Super działa 🙂 Udało nam się wyświetlić piąty element naszej kolekcji. Jednak wygląda to dość dziwnie. Ciekawie czy dałoby się to zrobić lepiej.
Może by tak użyć pętli for
lista = [1, 2, 3, 4, 5] def funkcja(kolekcja): for _ in range(5): temp = next(kolekcja) return temp piaty_element = funkcja(x * 2 for x in lista) print(f'piaty element to {piaty_element}')
Output
piaty element to 10
Niby działa, ale jednak nie o to nam chodziło. Udało nam się przy pomocy pętli powtórzyć wywoływanie funkcji next
odpowiednią ilość razy. Może by tak użyć enumerate
aby kontrolować index:
lista = [1, 2, 3, 4, 5] def funkcja(kolekcja): for i, vaulue in enumerate(kolekcja): if i == 4: return vaulue piaty_element = funkcja(x * 2 for x in lista) print(f'piaty element to {piaty_element}')
Output:
piaty element to 10
W ten sposób funkcja next
wywoływana jest za nas za pośrednictwem pętli for. Zauważ, że ciało naszej funkcji bardzo przypomina listę składaną.
Może by tak generator expression
lista = [1, 2, 3, 4, 5] def funkcja(kolekcja): return next( vaulue for i, vaulue in enumerate(kolekcja) if i == 4 ) piaty_element = funkcja(x * 2 for x in lista) print(f'piaty element to {piaty_element}')
Output:
piaty element to 10
Co tu się stało? Stworzyliśmy nowy generator opakowujący naszą kolekcję. Pierwsze wywołanie naszego nowego generatora zwróci piąty element z naszej kolekcji.
Pora na islice
import itertools lista = [1, 2, 3, 4, 5] def funkcja(kolekcja): return next(itertools.islice(kolekcja, 4, None)) piaty_element = funkcja(x * 2 for x in lista) print(f'piaty element to {piaty_element}')
Output
piaty element to 10
Funkcja islice przyjmuje trzy parametry:
- kolekcję na której będzie pracować
- index początku naszej nowej kolekcji
- index końca naszej nowej kolekcji (None oznacza że pobieramy tylko jeden element)
W naszym przypadku ze starej kolekcji wycinamy nasz piąty element.