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 nextodpowiednią 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.