Sequências infinitas em Python – Fibonacci como você nunca viu

sequencias_infinitas_fibonacci

Você sabia que é possível criar uma sequência infinita sem ter problemas de memória e ainda consumir essa sequência da forma que você quiser? À medida que seu entendimento da linguagem Python aumenta, você começa a explorar os módulos presentes na biblioteca padrão do Python. Hoje veremos um método específico do módulo itertools, o islice, que é excelente para nosso propósito. E usaremos a sequência de Fibonacci como exemplo.

Conhecendo o método

Sempre que ouvir falar de alguma funcionalidade de sua linguagem de programação favorita (ou da linguagem que paga suas contas…) busque conhecer a documentação. A documentação do módulo itertools o descreve como:

Esse módulo implementa diversos blocos de instruções com iteradores, inspirados por construções de APL, Haskell, e SML. Cada uma foi adequadamente reformulada para Python. Esse módulo padroniza um conjunto central de ferramentas rápidas e de uso eficiente da memória, que podem ser utilizadas sozinhas ou combinadas. Juntas, eles formam uma “álgebra de iteradores” tornando possível construir ferramentas sucintas e eficientes em Python puro. (…)

Veja que a documentação cita iteradores. Logo, caso não tenha muito familiaridade com o conceito, recomendo ler esse artigo aqui do site onde explico detalhadamente do que se trata. Depois volte para este artigo aqui :-).

Quanto ao método islice, a documentação o descreve como:

itertools.islice(iterable, stop) ou itertools.islice(iterable, start, stop[, step]). Cria um iterador que retorna elementos específicos de um iterável. (…)

Uma descrição bastante sucinta. Vamos começar importando o método:

from itertools import islice

Criando nossa sequência infinita

Agora vamos criar nossa sequência infinita. Se você já leu o artigo sobre geradores sabe que é perfeitamente possível criar tais sequências. Basta criar um gerador e gerar os valores sob demanda:

def pares_positivos():
    valor = 0
    while True:
        yield valor
        valor += 2

Perceba que o gerador vai gerar inteiros positivos a partir do zero sempre que solicitado. Vamos lembrar rapidamente o comportamento de um gerador como esse. Primeiro, vendo que o Python o reconhece como gerador:

pares_positivos()
<generator object pares_positivos at 0x7fac9d1deeb0>

Vamos associá-lo com uma variável para poder consumí-lo via chamadas next:

p = pares_positivos()
next(p)
0
next(p)
2
next(p)
4

E assim poderíamos seguir indefinidamente. Vamos agora entender o que faz o islice.

Consumindo a sequência de forma controlada

Vamos dar uma olhada na descrição do método com mais detalhes:

help(islice)
Help on class islice in module itertools:

class islice(builtins.object)
 |  islice(iterable, stop) --> islice object
 |  islice(iterable, start, stop[, step]) --> islice object
 |  
 |  Return an iterator whose next() method returns selected values from an
 |  iterable.  If start is specified, will skip all preceding elements;
 |  otherwise, start defaults to zero.  Step defaults to one.  If
 |  specified as another value, step determines how many values are
 |  skipped between successive calls.  Works like a slice() on a list
 |  but returns an iterator.
 |  
 |  Methods defined here:
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __reduce__(...)
 |      Return state information for pickling.
 |  
 |  __setstate__(...)
 |      Set state information for unpickling.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.

Perceba que há duas assinaturas. A primeira é islice(iterable, stop). Vamos então passar nosso iterável (o gerador) e um valor de parada, por exemplo, 10:

islice(p, 10)
<itertools.islice at 0x7fac9c16ee50>

Observe que realmente é um iterador. Vamos então consumí-lo, passando-o para uma tupla:

tuple(islice(p, 10))
(6, 8, 10, 12, 14, 16, 18, 20, 22, 24)

Veja que interessante. Já havíamos começado a consumir nosso gerador, chegando até o número 4. Agora foram gerador os próximos 10 inteiros sob demanda. Não houve necessidade de chamadas next nem de construir algum tipo de loop for ou algo do tipo. O islice cuida disso. E podemos solicitar os próximos 10 inteiros da mesma forma:

tuple(islice(p, 10))
(26, 28, 30, 32, 34, 36, 38, 40, 42, 44)

Temos agora perfeito controle sobre o consumo dessa sequência.

A própria função geradora poderia ser passada como argumento, por exemplo:

tuple(islice(pares_positivos(), 20))
(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38)

Foram gerados os 20 primeiros inteiros positivos pares diretamente.

A famosa sequência de Fibonacci

Convenhamos que a sequência de números pares positivos não é lá muito prática. Mas a sequência de Fibonacci é bem famosa e tem suas aplicações. Vamos utilizá-la e aprender um pouco mais sobre islice. Primeiro, vamos implementar um gerador para a sequência de Fibonacci:

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        b = a + b
        yield b
        a = a + b

Vamos ver o início da sequência para verificar nossa implementação:

f = fibonacci()
f
<generator object fibonacci at 0x7fac9c16a6d0>
next(f)
0
next(f)
1
next(f)
1
next(f)
2

OK, funcionando. Podemos, novamente usar o islice como fizemos anteriormente, vendo os 10 primeiros números da sequência:

tuple(islice(fibonacci(), 10))
(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)

Podemos, também, verificar como usar a segunda assinatura do método: islice(iterable, start, stop[, step]).

Por exemplo, podemos verificar quais os números entre as posições 5 e 10:

tuple(islice(fibonacci(), 5, 10))
(5, 8, 13, 21, 34)

Mas cuidado com a interpretação. O 5 se refere ao índice, e índices em Python começam em zero. Assim, veja que na realidade foram apresentados valores a partir do sexto na sequência (compare com o resultado da célula anterior). E o último índice é excluso, similar à um slice de listas em Python.

O último parâmetro é o step, também similar ao de listas:

tuple(islice(fibonacci(), 5, 10, 2))
(5, 13, 34)

Caso queira alguma posição em específico, basta chamar um next nesta posição e passar None como stop:

next(islice(fibonacci(), 5, None))
5

Conclusão

Mais uma etapa no entendimento e uso de geradores. Espero que os artigos estejam tendo uma boa conexão entre si e agregando conhecimento. Esse artigo faz parte da tag “Python Drops” aqui do site, de artigos curtos sobre fundamentos da linguagem. Compartilhe em suas redes e siga o projeto Ciência Programada para sempre estar atualizado. Até a próxima.

Compartilhe:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima