filter() – filtrando elementos de uma lista

Imagine uma situação na qual você tenha uma lista composta por uma grande quantidade de números, e deseja utilizar esses números para a realização de um cálculo. Porém, alguns desses valores devem ser descartados, por não se enquadrarem na faixa de valores que você pode utilizar nos cálculos. Como retirar os elementos que não cumprem os requisitos?

A solução mais óbvia seria percorrer a lista removendo os elementos que não cumpram o requisito, ou então, criar uma nova lista somente com os elementos que cumprem o requisito. Considere que o requisito seja: somente números positivos podem compor a lista. Vamos considerar a lista lista como exemplo:

lista = [8, 9, -1, -3, 3, -5, -4, 5, -4, 2, 5, 91, -11, 5, 10, 93, -75]
lista_valida = []
for elem in lista:
    if elem > 0:
        lista_valida.append(elem)
print lista_valida

O código acima cria uma nova lista (lista_valida) somente com os elementos positivos da lista lista. Funciona, certo? Sim, mas Python oferece uma forma mais prática e elegante para fazermos isso, a função filter(). Como o nome já diz, ela filtra uma lista. Ou seja, só “passam” os elementos que cumprirem os requisitos. A função filter() recebe dois parâmetros: uma função para validação de cada elemento, e uma sequência de valores (como uma lista, por exemplo). Vamos implementar o exemplo anterior utilizando filter().

lista = [8, 9, -1, -3, 3, -5, -4, 5, -4, 2, 5, 91, -11, 5, 56, 93, 13, 15, -75, 98]
def maior_que_zero(num):
    if num > 0:
        return True
 else:
        return False

lista_valida = filter(maior_que_zero, lista)
print lista_valida

Criamos uma função chamada maior_que_zero() que retorna True caso o valor recebido como parâmetro seja maior que 0 e False, caso contrário. Essa função vai ser chamada para cada elemento da lista lista, quando executarmos a função filter(), e essa, por sua vez, só vai incluir na lista nova (a ser retornada) os valores para os quais a execução de maior_que_zero() resultaram em True.

Vamos a outro exemplo: temos uma lista composta por strings e queremos obter uma lista contendo somente as strings que começam pela letra ‘a’ ou pela letra ‘e’.

palavras = ['teste', 'assistente', 'estou', 'aqui', 'onde', 'ouro', 'adeus']
def f(p):
    return p.startswith('a') or p.startswith('e')

print filter(f, palavras)

No código acima, aplicamos a função f() a cada elemento de palavras para filtrar essa lista. Ao final, filter() retorna uma lista contendo somente os elementos para os quais a expressão p.startswith(‘a’) or p.startswith(‘e’) retorna True (que são os elementos que iniciam por ‘a’ ou por ‘e’).

Esse é mais um dos tantos recursos interessantes que Python nos oferece e que podemos utilizar em nosso benefício. Teste os códigos acima e faça seus próprios testes.

Acessando o último elemento de uma lista

Essa vai ser uma dica rápida, mas muito útil…

Consideremos uma lista qualquer contendo alguns números inteiros:

>>> L = [1, 1, 2, 3, 5, 8, 13, 21]

Como podemos acessar o último elemento dessa lista? Nessa lista específica, poderíamos acessar o último elemento assim: L[7]. Mas é claro que isso funciona só para a lista L com 8 elementos. E para acessar o último elemento de uma lista qualquer?

>>> L[len(L)-1]
21

O código acima é uma das formas de acessar o último elemento de uma lista, mas não é a forma mais conveniente. Python nos oferece uma forma muito melhor para fazer isso:

>>> L[-1]
21

Também podemos acessar o penúltimo elemento através do índice -2, e assim por diante. Legal né? Pois é, mais uma das coisas que tornam Python tão legal. 🙂

range() vs xrange()

(válido somente para Python 2.x)

A função range()

Em Python, é muito comum usarmos a seguinte estrutura para realizar uma repetição baseada em um contador:

for i in range(0, 10):
    print i,

A função range(x, y) gera uma lista de números inteiros de x até y (sem incluir o segundo). Assim, range(0, 10), gera a seguinte lista:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Desse modo, a variável i é iterada sobre essa lista, através da estrutura de repetição for. O trecho de código:

for i in range(0, 10):
    print i,

pode ser lido como:

"Para cada elemento na lista [0,1,2,3,4,5,6,7,8,9], imprima tal elemento"

Usar range() ou xrange()? Eis a questão…

É comum ouvirmos ou lermos algum desenvolvedor Python aconselhando a utilização da função xrange() ao invés da função range(), por questões de desempenho. Mas o que é essa tal de xrange()?

A xrange() nada mais é do que uma função que pode, em muitos casos (não sempre), substituir o uso da função range(), fornecendo ainda melhor desempenho. Veja o código abaixo:

for i in xrange(0, 10):
    print i,

O resultado dessa execução é o mesmo de quando utilizamos a função range(), porém, por gerar um elemento de cada vez, o código que utiliza a função xrange() apresenta um desempenho superior.

Quando executamos:

for i in range(0, 1000000000):
    pass

A função range() irá imediatamente gerar uma lista contendo um bilhão de inteiros e alocar essa lista na memória. Uma lista contendo um bilhão de inteiros é capaz de encher a memória de um computador pessoal.

Já com a função xrange(), ao executarmos:

for i in xrange(0, 1000000000):
    pass

Cada um dos inteiros (dos 1 bilhão) será gerado de uma vez, economizando memória e tempo de startup.

Vamos então testar o desempenho usando o módulo timeit().

 

A hora da verdade

junior@qwerty:~ $ python -m timeit "for i in xrange(10000000): pass"
 10 loops, best of 3: 246 msec per loop
junior@qwerty:~-$ python -m timeit "for i in range(10000000): pass"
 10 loops, best of 3: 342 msec per loop

Como podemos ver, o loop que utiliza a função xrange() foi quase 100 milisegundos mais rápido do que o loop que utiliza a função range(). Além disso, se fizermos uma análise de consumo de memória, veremos que a o código que utiliza a função range() utiliza uma quantidade de memória muito maior, pois gera a lista inteira antes de executar a iteração do for.

Então nunca mais vou usar o range(), certo?

Errado! Existe uma diferença fundamental entre as duas funções: a função xrange() não gera uma lista. Isso torna inviável, por exemplo, o slicing e a gravação de seu resultado como uma lista em uma variável. Vejamos:

>>> x = range(0, 10)
>>> print x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> y = xrange(0, 10)
>>> print y
xrange(10)

Ou seja, apesar de nos fornecer um desempenho superior ao desempenho obtido com a range(), a função xrange() não substitui a anterior em todos os seus casos.

List Comprehensions

Desde sua versão 2.0, Python oferece uma construção chamada list comprehensions, que é capaz de gerar listas de valores de forma bastante clara e concisa. Considere a notação matemática de conjunto, utilizada para descrever um conjunto composto pelo dobro de cada um dos números naturais de 0 até 9:

Tal notação descreve um conjunto de elementos através de propriedades que seus elementos devem satisfazer. A expressão acima pode também ser lida como:

S é um conjunto que contém como elementos os dobros de todos números naturais menores que 10.

Resultando no seguinte conjunto:

{0, 2, 4, 6, 8, 10, 12, 14, 16, 18}

Em Python, é possível descrevermos uma lista através de uma construção semelhante à notação matemática para descrição de conjuntos. Veja o exemplo abaixo, que apresenta tal construção, chamada de list comprehensions:

>>> S = [x*2 for x in range(0, 10)]
>>> print S
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Como podemos ver, foi gerada uma lista com os quadrados dos números de 0 a 9. Ou seja, a expressão acima pode ser lida como:

Gere uma lista contendo o dobro de cada número inteiro na faixa de 0 a 9.

Também é permitido que sejam utilizadas expressões condicionais dentro de list comprehensions:

>>> S = [x*2 for x in range(0,10) if x % 2 == 0]
>>> print S
[0, 4, 8, 12, 16]

Ou seja, gere uma lista contendo o dobro de todo elemento x, sendo x um número entre 0 e 9, se x for um número par. O seguinte trecho de código obtém o mesmo resultado, sem a utilização de list comprehensions.

S = []
for x in range(0,10):
    if x % 2 == 0:
        S.append(x*2)

Apesar de inspirada na notação matemática, essa construção também pode ser utilizada para conjuntos não-numéricos:

>>> [s.capitalize() for s in ['um', 'dois', 'tres']]
['Um', 'Dois', 'Tres']

Também é possível gerar uma lista de listas como resultado:

>>> [[s.capitalize(), s.upper(), len(s)] for s in ['um', 'dois', 'tres']]
[['Um', 'UM', 2], ['Dois', 'DOIS', 4], ['Tres', 'TRES', 4]]

Ou então, uma lista de tuplas:

>>> [(s.capitalize(), s.upper(), len(s)) for s in ['um', 'dois', 'tres']]
[('Um', 'UM', 2), ('Dois', 'DOIS', 4), ('Tres', 'TRES', 4)]

Como você pode ver, a estrutura de list comprehensions é bastante flexível. Descubra mais detalhes em: http://docs.python.org/tutorial/datastructures.html#list-comprehensions