Map, filter e reduce na prática

1. Introdução às Funções de Alta Ordem

Python trata funções como objetos de primeira classe — você pode passá-las como argumentos, retorná-las de outras funções e armazená-las em variáveis. Esse recurso é a base do paradigma funcional, que nos permite escrever código mais declarativo e menos propenso a efeitos colaterados.

Considere a diferença entre um loop tradicional e uma abordagem funcional:

# Loop tradicional
numeros = [1, 2, 3, 4, 5]
quadrados = []
for n in numeros:
    quadrados.append(n ** 2)

# Abordagem funcional
quadrados = list(map(lambda x: x ** 2, numeros))

A versão funcional é mais concisa, expressa a intenção diretamente ("mapeie cada elemento para seu quadrado") e elimina variáveis mutáveis intermediárias. As três funções protagonistas desse estilo são map(), filter() e reduce(), disponíveis nativamente (as duas primeiras como built-ins, a última no módulo functools).

2. map(): Transformando Iteráveis

A função map() aplica uma transformação a cada elemento de um ou mais iteráveis, retornando um iterador lazy. Sua sintaxe é map(função, iterável, ...).

Exemplo 1 — Conversão de tipos e formatação:

# Converter strings para inteiros
valores = ["10", "20", "30", "40"]
inteiros = list(map(int, valores))  # [10, 20, 30, 40]

# Formatar preços com duas casas decimais
precos = [49.9, 129.5, 9.99]
formatados = list(map("R$ {:.2f}".format, precos))
# ['R$ 49.90', 'R$ 129.50', 'R$ 9.99']

Exemplo 2 — Lambda para transformações rápidas:

nomes = ["ana", "joão", "maria"]
nomes_capitalizados = list(map(lambda s: s.capitalize(), nomes))
# ['Ana', 'João', 'Maria']

Exemplo 3 — Múltiplos iteráveis simultâneos:

lista1 = [1, 2, 3, 4]
lista2 = [10, 20, 30, 40]
produtos = list(map(lambda x, y: x * y, lista1, lista2))
# [10, 40, 90, 160]

Quando múltiplos iteráveis são fornecidos, map() para quando o menor deles se esgota.

3. filter(): Selecionando Dados com Critérios

filter() constrói um iterador contendo apenas os elementos para os quais a função booleana retorna True. Sintaxe: filter(função_booleana, iterável).

Exemplo 1 — Filtrar números pares:

numeros = range(1, 11)
pares = list(filter(lambda x: x % 2 == 0, numeros))
# [2, 4, 6, 8, 10]

Exemplo 2 — Remover valores nulos ou falsy:

dados = [0, "Python", "", None, 42, False, "map"]
limpos = list(filter(None, dados))
# ['Python', 42, 'map']

Passar None como função faz com que filter() remova todos os valores considerados falsy (0, None, False, "", [], etc).

Exemplo 3 — Filtrar com funções mais complexas:

def tem_letra_a(palavra):
    return 'a' in palavra.lower()

palavras = ["casa", "carro", "moto", "avião", "navio"]
com_a = list(filter(tem_letra_a, palavras))
# ['casa', 'carro', 'avião', 'navio']

Comparação com list comprehension: filter() é mais explícito semanticamente — você está filtrando, não apenas iterando. List comprehensions com if são mais flexíveis, mas filter() comunica melhor a intenção de seleção.

4. reduce(): Acumulando Resultados

Diferente de map() e filter(), reduce() não é uma built-in — precisa ser importada de functools. Ela aplica uma função de dois argumentos cumulativamente aos elementos de um iterável, reduzindo-o a um único valor.

from functools import reduce

Sintaxe: reduce(função_acumuladora, iterável, valor_inicial_opcional)

Exemplo 1 — Soma acumulada:

numeros = [1, 2, 3, 4, 5]
soma = reduce(lambda acc, x: acc + x, numeros, 0)
# 15

Exemplo 2 — Produto de todos os elementos:

fatores = [2, 3, 4, 5]
produto = reduce(lambda acc, x: acc * x, fatores, 1)
# 120

Exemplo 3 — Encontrar o máximo:

valores = [45, 12, 89, 33, 71]
maximo = reduce(lambda a, b: a if a > b else b, valores)
# 89

Cuidados: reduce() pode tornar o código menos legível. Para operações comuns como soma, produto, mínimo ou máximo, prefira funções built-in (sum(), min(), max()) ou loops explícitos.

5. Combinações Poderosas: Encadeando Funções

O verdadeiro poder surge ao combinar map(), filter() e reduce() em pipelines de processamento.

Exemplo real — Processamento de dados de vendas:

from functools import reduce

vendas = [
    {"produto": "notebook", "quantidade": 3, "preco": 3500.00},
    {"produto": "mouse", "quantidade": 10, "preco": 50.00},
    {"produto": "teclado", "quantidade": 5, "preco": 200.00},
    {"produto": "monitor", "quantidade": 2, "preco": 1200.00},
    {"produto": "mousepad", "quantidade": 0, "preco": 30.00},
]

# Pipeline: filtrar itens com quantidade > 0, calcular total por item, somar tudo
total_vendas = reduce(
    lambda acc, x: acc + x,
    map(
        lambda v: v["quantidade"] * v["preco"],
        filter(lambda v: v["quantidade"] > 0, vendas)
    ),
    0.0
)
# total_vendas = 3*3500 + 10*50 + 5*200 + 2*1200 = 10500 + 500 + 1000 + 2400 = 14400.0

Comparação com compreensões aninhadas:

# Equivalente mais legível
total_vendas = sum(v["quantidade"] * v["preco"] for v in vendas if v["quantidade"] > 0)

Para pipelines simples, comprehensions ou generator expressions são mais pythonicos. Para transformações complexas com múltiplos estágios, o encadeamento funcional pode ser mais claro.

6. Alternativas Modernas e Boas Práticas

List comprehensions são o substituto idiomático para map() e filter() na maioria dos casos:

# map com lambda
quadrados = list(map(lambda x: x**2, range(10)))
# Equivalente idiomático
quadrados = [x**2 for x in range(10)]

# filter com lambda
pares = list(filter(lambda x: x % 2 == 0, range(10)))
# Equivalente idiomático
pares = [x for x in range(10) if x % 2 == 0]

Generator expressions oferecem lazy evaluation, assim como map() e filter():

# Lazy com map
quadrados_lazy = map(lambda x: x**2, range(10))

# Lazy com generator expression
quadrados_lazy = (x**2 for x in range(10))

Quando evitar reduce(): Para operações que possuem funções built-in, use-as:

# Evite
soma = reduce(lambda a, b: a + b, numeros, 0)
# Prefira
soma = sum(numeros)

# Evite
todos_pares = reduce(lambda a, b: a and b % 2 == 0, numeros, True)
# Prefira
todos_pares = all(x % 2 == 0 for x in numeros)

Regra prática: Use map() e filter() quando já tiver uma função nomeada para aplicar; use list comprehensions para transformações simples com lambdas; use reduce() apenas quando não existir alternativa built-in e a lógica de acumulação for clara.

7. Casos Práticos no Mundo Real

Processamento de logs — filtrar erros e extrair informações:

logs = [
    "2024-01-15 10:30:45 INFO Conexão estabelecida",
    "2024-01-15 10:31:02 ERROR Timeout ao acessar banco",
    "2024-01-15 10:31:15 WARNING Pouca memória disponível",
    "2024-01-15 10:32:00 ERROR Falha na autenticação",
]

# Extrair apenas mensagens de erro
erros = list(filter(lambda linha: "ERROR" in linha, logs))

# Extrair timestamps dos erros
timestamps_erro = list(map(lambda linha: linha.split()[0:2], erros))
# [['2024-01-15', '10:31:02'], ['2024-01-15', '10:32:00']]

Análise financeira — média móvel com reduce():

from functools import reduce

precos_acoes = [100, 102, 101, 105, 110, 108, 107, 112, 115, 113]
janela = 3

def media_moveis(dados, janela):
    return reduce(
        lambda acc, _: acc + [sum(dados[len(acc):len(acc)+janela]) / janela],
        range(len(dados) - janela + 1),
        []
    )

medias = media_moveis(precos_acoes, janela)
# [101.0, 102.67, 105.33, 107.67, 108.33, 109.0, 111.33, 113.33]

Limpeza de dados — normalizar strings e remover outliers:

dados_brutos = ["  João  ", "MARIA", "ana", None, "PEDRO", "", "  "]

# Normalizar: remover espaços, capitalizar, ignorar nulos/vazios
limpos = list(filter(None, map(lambda s: s.strip().capitalize() if s else None, dados_brutos)))
# ['João', 'Maria', 'Ana', 'Pedro']

Combinando com itertools para processamento avançado:

from itertools import islice, chain

# Processar em lotes de 3
dados = range(1, 13)
lotes = [list(islice(dados, i, i+3)) for i in range(0, 12, 3)]
somas_lotes = list(map(sum, lotes))
# [6, 15, 24, 33]

Esses exemplos mostram como map(), filter() e reduce() resolvem problemas reais de processamento de dados de forma elegante e funcional. A escolha entre eles e alternativas modernas depende sempre do contexto, legibilidade e desempenho desejado.


Referências