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

    Documento e caixa de entrada representando caixas de e-mail com mailbox no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    mailbox no Python: caixas de e-mail

    Aprenda mailbox no Python para ler, criar e migrar caixas Maildir, mbox e MH com locking, mensagens, flags e tratamento

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Editor de texto representando formatação com textwrap no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    textwrap no Python: formate textos

    Aprenda textwrap no Python para quebrar, preencher, encurtar, indentar e remover recuos de textos com controle de largura e espaços.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Pasta e lupa representando filtros de nomes com fnmatch no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    fnmatch no Python: filtre nomes de arquivos

    Aprenda fnmatch no Python para filtrar nomes de arquivos com curingas, controlar maiúsculas, excluir padrões e evitar confundir glob com

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor com dados binários representando arrays numéricos compactos no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    array no Python: números compactos

    Aprenda array no Python para armazenar números compactos, manipular bytes, arquivos binários e buffers com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Círculo cromático representando conversões RGB, HSV e HLS com colorsys no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    colorsys no Python: RGB, HSV e HLS

    Aprenda colorsys no Python para converter cores entre RGB, HSV, HLS e YIQ, gerar paletas e evitar erros com escalas

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Ícone de configuração representando arquivos plist com plistlib no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    plistlib no Python: arquivos plist

    Aprenda plistlib no Python para ler e gravar arquivos plist XML e binários, validar dados e integrar configurações Apple com

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026