Ir para o conteúdo principal

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

·1038 palavras·5 minutos·
Programação Python Itertools Islice Drops
Autor
Francisco Bustamante
Um químico trabalhando com Ciência de Dados e Programação em Python.
Tabela de conteúdos

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.

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.

Gostou desse artigo? Ele faz parte do Drops de programação, um conjunto de posts mais curtos voltados para fundamentos falando sobre alguns aspectos da linguagem Python e de programação em geral. Você pode ler mais desses artigos buscando a tag “drops aqui no site.

Até a próxima!

Relacionados

Funções any e all em Python
·2245 palavras·11 minutos
Programação Python Any All Drops
Neste artigo, vamos ver como funcionam as funções all e any, presentes numa instalação padrão da linguagem Python. E, mais, vamos ver a utilidade de cada uma dessas funções e cuidados ao utilizá-las com base em uma análise de complexidade.
Geradores em Python - Códigos até 1000 vezes mais rápidos
·1938 palavras·10 minutos
Programação Python Geradores Yield Drops
Você sabe a diferença entre uma função “normal” e uma função geradora em Python? Qual a diferença entre o return de uma função usual e o yield de um gerador? Nesse artigo responderemos essas perguntas e ainda nos aprofundaremos em alguns aspectos da linguagem. Veremos como geradores em Python podem tornar seu código até 1000 vezes mais rápidos.
Iteradores e iteráveis em Python
·1424 palavras·7 minutos
Programação Python Iterador Iterável Drops
Você sabe o que é um iterável? E um iterador? Como reconhecer essas estruturas em Python? Responder tais questionamentos é o objetivo desse artigo.