bisect no Python: listas sempre ordenadas

Publicado em: 27/07/2026
Tempo de leitura: 8 minutos
Monitor com busca binária e listas ordenadas no Python

Manter uma lista em ordem parece simples: adicione o novo item e chame sort(). Essa abordagem funciona para conjuntos pequenos ou atualizações raras, mas repete trabalho quando o programa recebe valores continuamente e precisa consultar faixas, limites ou posições de inserção. O módulo bisect no Python resolve esse cenário com busca binária. Ele encontra rapidamente o ponto correto em uma sequência já ordenada e permite inserir novos elementos sem procurar a posição por varredura linear.

Neste guia, você aprenderá a diferença entre bisect_left() e bisect_right(), como usar insort(), como pesquisar registros com key, quando pré-calcular chaves e quais limites de desempenho considerar. O assunto complementa os artigos sobre ordenação com sort e sorted, listas em Python, funções em Python, estruturas do módulo collections e análise de desempenho com cProfile.

O que é o módulo bisect?

O módulo bisect implementa o algoritmo de bisseção para localizar posições em listas ordenadas. Em vez de comparar o valor procurado com todos os elementos, a busca verifica o centro da faixa, elimina metade das possibilidades e repete o processo. Por isso, localizar uma posição custa O(log n).

A documentação oficial do bisect destaca uma característica importante: as funções procuram pontos de inserção. Elas não chamam __eq__() para confirmar igualdade; usam comparações de ordem e devolvem um índice entre os elementos. Isso torna a API útil para inserções, consultas de intervalo e pesquisas personalizadas.

A lista precisa estar ordenada

O contrato mais importante é simples: a sequência já deve estar ordenada pelo mesmo critério usado na busca. Se a lista estiver fora de ordem, o índice devolvido pode parecer válido, mas não terá significado confiável.

from bisect import bisect_left

numeros = [3, 8, 12, 19, 25]
indice = bisect_left(numeros, 15)

print(indice)  # 3
print(numeros[:indice])
print(numeros[indice:])

O resultado indica que o número 15 deveria ser inserido antes do 19, no índice 3. A função não altera a lista. Ela apenas calcula a posição.

bisect_left e bisect_right

A diferença aparece quando o valor já existe. bisect_left() devolve a posição anterior ao primeiro item igual. bisect_right(), também disponível pelo alias bisect(), devolve a posição após o último item igual.

from bisect import bisect_left, bisect_right

valores = [10, 20, 20, 20, 30]
inicio = bisect_left(valores, 20)
fim = bisect_right(valores, 20)

print(inicio)  # 1
print(fim)     # 4
print(valores[inicio:fim])

Essa combinação permite encontrar rapidamente o intervalo ocupado por valores repetidos. A quantidade de ocorrências é fim - inicio. O mesmo padrão funciona com datas, pontuações, preços e outras chaves ordenáveis.

Como procurar um valor exato

Como bisect_left() retorna um ponto de inserção mesmo quando o valor não existe, verifique o índice antes de considerar a busca bem-sucedida:

from bisect import bisect_left

def encontrar_indice(valores, procurado):
    indice = bisect_left(valores, procurado)
    if indice != len(valores) and valores[indice] == procurado:
        return indice
    raise ValueError(f"{procurado!r} não encontrado")

print(encontrar_indice([2, 5, 9, 14], 9))

Para várias consultas exatas por chave, um dicionário costuma ser mais apropriado. O bisect se destaca quando você precisa localizar limites, vizinhos ou posições em uma faixa ordenada.

Inserindo com insort

insort_left() e insort_right() combinam a busca da posição com list.insert(). A primeira coloca o novo valor antes dos iguais; a segunda coloca depois.

from bisect import insort

fila = [4, 9, 15, 22]
insort(fila, 12)
insort(fila, 9)

print(fila)

Usar append() quebraria a ordenação quando o valor não fosse maior que o último item. Ordenar toda a lista após cada inserção também funcionaria, porém faria mais trabalho do que localizar diretamente o ponto adequado.

O custo real da inserção

A busca binária custa O(log n), mas inserir no meio de uma lista custa O(n), pois os elementos seguintes precisam ser deslocados. Portanto, insort() é conveniente e eficiente para consultas frequentes com um volume moderado de inserções, mas não transforma a lista em uma estrutura de inserção logarítmica.

Quando há milhões de atualizações, considere uma árvore balanceada, banco de dados indexado ou biblioteca de coleções ordenadas. Quando o programa só adiciona valores no final em ordem crescente, append() continua sendo a opção mais simples e rápida. A documentação oficial sobre estruturas de dados ajuda a comparar operações comuns de listas, filas e outras coleções.

Limitando a busca com lo e hi

Os parâmetros lo e hi restringem a pesquisa a uma parte da lista. Isso é útil quando outra etapa já delimitou uma região relevante.

from bisect import bisect_left

dados = [2, 5, 8, 11, 14, 17, 20]
indice = bisect_left(dados, 13, lo=2, hi=6)
print(indice)

Os limites seguem a convenção de fatias: lo é incluído e hi é excluído. Passe índices coerentes com a ordenação e evite usar esses parâmetros apenas como micro-otimização sem medição.

Pesquisando objetos com key

Desde o Python 3.10, as funções aceitam o argumento key. Ele extrai a chave de comparação de cada elemento armazenado. Na busca, a função não é aplicada ao valor x; você fornece diretamente a chave procurada.

from bisect import bisect_left
from operator import itemgetter

produtos = [
    {"nome": "Caderno", "preco": 18.0},
    {"nome": "Mouse", "preco": 65.0},
    {"nome": "Teclado", "preco": 120.0},
]

por_preco = itemgetter("preco")
indice = bisect_left(produtos, 70.0, key=por_preco)
print(produtos[indice]["nome"])

Os registros precisam estar ordenados pela mesma função key. Caso contrário, a pesquisa estará usando um critério diferente daquele que organizou a lista.

Inserindo registros com key

Nas funções insort, a chave é aplicada ao item novo durante a etapa de pesquisa, mas o objeto completo é inserido na lista.

from bisect import insort
from operator import itemgetter

tarefas = [
    {"prioridade": 1, "titulo": "Restaurar serviço"},
    {"prioridade": 3, "titulo": "Gerar relatório"},
]

nova = {"prioridade": 2, "titulo": "Responder cliente"}
insort(tarefas, nova, key=itemgetter("prioridade"))
print([t["prioridade"] for t in tarefas])

Para empates, escolha conscientemente entre insort_left e insort_right. Se a ordem de chegada precisar ser estável, inclua um contador crescente na chave ou no registro.

Pré-calculando chaves

As funções de busca são sem estado e não armazenam resultados da função key. Em um loop, a mesma chave pode ser calculada repetidamente. Se a extração for cara, mantenha uma lista paralela de chaves.

from bisect import bisect_left

clientes = [
    ("Ana", 1200),
    ("Bruno", 2500),
    ("Carla", 4100),
]

saldos = [cliente[1] for cliente in clientes]
indice = bisect_left(saldos, 3000)
print(clientes[indice])

Ao inserir ou remover registros, atualize as duas listas na mesma operação. Outra opção indicada pela documentação é aplicar functools.cache() quando a função de chave recebe valores reutilizáveis e puros.

Consultas por faixa

Uma aplicação frequente é selecionar todos os valores entre dois limites. Use bisect_left() no limite inferior e bisect_right() no superior:

from bisect import bisect_left, bisect_right

temperaturas = [12, 15, 18, 18, 21, 24, 27, 30]
inicio = bisect_left(temperaturas, 18)
fim = bisect_right(temperaturas, 24)

print(temperaturas[inicio:fim])

A busca dos dois limites custa O(log n). Criar a fatia custa tempo proporcional à quantidade de itens devolvidos. Quando você só precisa contar, use fim - inicio e evite copiar os elementos.

Classificando valores por intervalos

bisect() também pode transformar limites ordenados em categorias. O índice retornado seleciona a faixa correspondente.

from bisect import bisect

limites = [60, 70, 80, 90]
conceitos = "FDCBA"

def conceito(nota):
    return conceitos[bisect(limites, nota)]

print(conceito(77))
print(conceito(90))

O padrão serve para faixas de imposto, níveis de risco, tamanhos de pacote e regras de preço. Documente claramente se o limite pertence à faixa anterior ou posterior, pois essa decisão determina o uso de bisect_left ou bisect_right.

Encontrando vizinhos

O índice de inserção permite encontrar o valor imediatamente menor ou maior. Após bisect_left(valores, x), o item em indice - 1 é o maior valor menor que x, quando o índice é maior que zero. O item no próprio índice é o primeiro maior ou igual, quando o índice ainda está dentro da lista.

from bisect import bisect_left

horarios = [8, 10, 13, 16, 19]
indice = bisect_left(horarios, 14)

anterior = horarios[indice - 1] if indice else None
proximo = horarios[indice] if indice != len(horarios) else None
print(anterior, proximo)

Esse padrão é útil para calendários, séries temporais, versões e pontos de medição.

bisect, dicionário, set ou heap?

Use bisect quando a ordem é importante e você precisa de limites, vizinhos ou faixas. Use dicionário ou set para localizar valores exatos com alta frequência. Use heapq quando precisa retirar repetidamente o menor ou maior elemento, mas não necessita pesquisar posições arbitrárias.

Um banco de dados com índice é mais apropriado quando os dados não cabem em memória, precisam de persistência ou recebem atualizações concorrentes. A estrutura correta depende do padrão de leitura e escrita, não apenas da complexidade da busca isolada.

Segurança em múltiplas threads

As funções do módulo não são seguras para alterações concorrentes na mesma sequência. Se uma thread modificar a lista enquanto outra executa a busca ou inserção, a ordenação pode ser corrompida e o comportamento fica indefinido. Proteja a operação completa com um bloqueio ou mantenha estruturas separadas por thread.

Não basta bloquear somente bisect() e liberar antes de insert(). Outra thread poderia alterar a lista entre as duas etapas. O cálculo da posição e a inserção precisam formar uma seção crítica única.

Erros comuns

  • Aplicar bisect a uma lista que não está ordenada.
  • Ordenar por uma chave e pesquisar usando outra.
  • Interpretar o índice retornado como confirmação de que o valor existe.
  • Ignorar a diferença entre inserção à esquerda e à direita.
  • Esperar inserções O(log n) em uma lista comum.
  • Recalcular uma função key cara em milhares de buscas.
  • Modificar a lista concorrentemente sem sincronização.

Como testar

Teste as bordas: lista vazia, valor menor que todos, maior que todos, duplicatas e limites exatos. Verifique também se a lista continua ordenada após uma sequência de inserções.

from bisect import insort

def test_insercoes_preservam_ordem():
    valores = []
    for numero in [9, 2, 7, 7, 1]:
        insort(valores, numero)
    assert valores == [1, 2, 7, 7, 9]

Para funções de faixa, inclua casos sem resultados e casos em que todos os itens pertencem ao intervalo. Isso evita erros de índices e decisões incorretas sobre limites inclusivos.

Boas práticas

  • Centralize pesquisas e inserções em funções pequenas.
  • Documente o critério de ordenação e o tratamento de duplicatas.
  • Use key consistente com a ordenação inicial.
  • Pré-calcule chaves quando a extração for cara.
  • Evite copiar fatias quando só precisa contar elementos.
  • Meça inserções grandes antes de escolher uma lista.
  • Proteja a operação completa quando houver concorrência.

Conclusão

O bisect no Python transforma listas ordenadas em estruturas eficientes para localizar pontos de inserção, limites e vizinhos. bisect_left e bisect_right controlam o tratamento de duplicatas, enquanto insort mantém a ordem após novas entradas. O argumento key amplia a técnica para dicionários, tuplas e objetos.

A busca é logarítmica, mas a inserção em listas continua linear. Por isso, o módulo é excelente para muitas leituras e um número moderado de atualizações. Quando o volume, a concorrência ou a persistência crescem, outra estrutura pode ser melhor. Com o contrato de ordenação bem definido e testes de borda, bisect oferece uma solução simples, previsível e poderosa.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Desenvolvedor implementando fila de prioridade com heapq no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    heapq no Python: filas de prioridade

    Aprenda heapq no Python para criar filas de prioridade, encontrar menores valores e processar tarefas com heaps eficientes.

    Ler mais

    Tempo de leitura: 7 minutos
    26/07/2026
    Como acelerar código Python usando lru cache
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como acelerar seu código Python com @lru_cache em 2 minutos

    Você já sentiu que seu programa está demorando uma eternidade para processar cálculos repetitivos? Sabia que existe uma forma mágica

    Ler mais

    Tempo de leitura: 10 minutos
    06/04/2026
    Leitura segura de senhas no terminal usando Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como ler senhas de forma segura no terminal com Python

    Entender como ler senhas de forma segura no terminal com Python é um passo fundamental para qualquer desenvolvedor que deseja

    Ler mais

    Tempo de leitura: 8 minutos
    02/04/2026
    Como evitar KeyError usando defaultdict em Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como evitar KeyError usando defaultdict no Python

    Você já tentou acessar uma chave em um dicionário e se deparou com aquele erro vermelho interrompendo seu script? O

    Ler mais

    Tempo de leitura: 9 minutos
    30/03/2026
    Monitoramento de pastas em tempo real com Python Watchdog
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como monitorar pastas em tempo real com Python e watchdog

    Monitorar pastas em tempo real com Python e watchdog é uma das formas mais eficientes de criar sistemas automatizados que

    Ler mais

    Tempo de leitura: 9 minutos
    26/03/2026
    Instalação offline de pacotes Python sem internet
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Sem internet? Instale pacotes Python offline em minutos

    Você já se deparou com a situação frustrante de precisar instalar uma biblioteca específica em um servidor isolado ou em

    Ler mais

    Tempo de leitura: 11 minutos
    25/03/2026