bisect en Python: listas ordenadas

Publicado el: 08/08/2026
Tempo de leitura: 5 minutos
Búsqueda binaria y listas ordenadas con bisect en Python

El módulo bisect de la biblioteca estándar resuelve un problema concreto: encontrar rápidamente la posición correcta de un valor dentro de una lista que ya está ordenada. En lugar de recorrer todos los elementos, aplica búsqueda binaria y descarta la mitad del intervalo restante en cada comparación. Es una herramienta útil para rankings, bandas de precio, umbrales, horarios, catálogos y colecciones en memoria con muchas lecturas.

En esta guía aprenderás a usar bisect_left, bisect_right, insort_left e insort_right, además de comprender sus costos reales, el tratamiento de duplicados, el parámetro key y los errores más comunes.

Por qué importa la búsqueda binaria

Una búsqueda lineal puede revisar muchos elementos. La búsqueda binaria necesita muchas menos comparaciones porque reduce el espacio de búsqueda de forma exponencial. Encontrar una posición es una operación logarítmica. Insertar en una lista de Python sigue siendo lineal, porque las referencias posteriores pueden necesitar desplazarse.

Por eso, bisect funciona especialmente bien cuando hay muchas consultas y pocas inserciones. Una base de datos, un árbol balanceado, un heap o una colección ordenada especializada puede ser mejor para cargas con cambios constantes.

bisect_left y bisect_right

from bisect import bisect_left, bisect_right

valores = [10, 20, 20, 20, 30, 40]

inicio = bisect_left(valores, 20)
fin = bisect_right(valores, 20)

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

bisect_left devuelve la primera posición válida para el objetivo. bisect_right devuelve la posición inmediatamente posterior a todos los valores iguales. Juntas, estas funciones permiten contar duplicados, extraer intervalos iguales y elegir si un nuevo elemento debe aparecer antes o después de sus equivalentes.

Insertar sin perder el orden

from bisect import insort_left, insort_right

notas = [5.0, 6.5, 8.0, 9.5]
insort_left(notas, 8.0)
insort_right(notas, 8.0)
print(notas)

Las funciones insort combinan la localización de la posición y la inserción. El código queda más claro que una búsqueda manual seguida de list.insert. La búsqueda es rápida, pero el movimiento de elementos sigue dominando el costo de la operación.

Buscar un valor exacto

from bisect import bisect_left

def buscar_exacto(lista, objetivo):
    posicion = bisect_left(lista, objetivo)
    if posicion != len(lista) and lista[posicion] == objetivo:
        return posicion
    return -1

El índice devuelto es solo una posición de inserción. No demuestra que el elemento exista. Siempre valida el límite y compara el valor antes de considerar la búsqueda exitosa.

Crear tablas de rangos

from bisect import bisect_right

limites = [100, 250, 500, 1000]
etiquetas = ["mínimo", "pequeño", "medio", "grande", "empresa"]

def clasificar(valor):
    return etiquetas[bisect_right(limites, valor)]

Este patrón convierte límites ordenados en una tabla de clasificación rápida. Puede aplicarse a tarifas de envío, niveles de precio, categorías de latencia, impuestos, puntuaciones y reglas de servicio.

Usar el parámetro key

Las versiones modernas de Python permiten usar una función key para extraer la clave de comparación de cada elemento almacenado. La clave se aplica a los elementos de la lista, pero no al valor buscado. Por eso, el argumento x debe ser una clave compatible.

from bisect import bisect_left

productos = [
    {"nombre": "A", "precio": 10},
    {"nombre": "B", "precio": 25},
    {"nombre": "C", "precio": 40},
]

posicion = bisect_left(productos, 30, key=lambda item: item["precio"])
print(posicion)

Las búsquedas repetidas pueden ejecutar la función de clave muchas veces. Si calcularla es costoso, conviene mantener una lista paralela con claves precalculadas o aplicar caché.

Listas paralelas de claves

from bisect import bisect_left

registros = [
    {"id": 101, "nombre": "Ana"},
    {"id": 205, "nombre": "Bruno"},
    {"id": 330, "nombre": "Carla"},
]
ids = [registro["id"] for registro in registros]

posicion = bisect_left(ids, 205)
if posicion < len(ids) and ids[posicion] == 205:
    print(registros[posicion])

Este diseño evita recalcular la clave. Su riesgo principal es la sincronización: inserciones, eliminaciones y cambios deben modificar ambas listas en posiciones equivalentes. Encapsularlas en una clase ayuda a proteger esa regla.

Errores comunes

El primer error es usar bisect sobre datos desordenados. El módulo no valida el orden y puede devolver un índice aparentemente razonable pero incorrecto. El segundo error es creer que la inserción también es logarítmica. La búsqueda lo es; la modificación de la lista es lineal. El tercero es tratar la posición de inserción como prueba de existencia. El cuarto es ordenar con un criterio y buscar con otro.

La concurrencia introduce otro riesgo. Una búsqueda seguida de una inserción no debe considerarse atómica cuando varios threads o procesos modifican la misma colección. Protege la operación con sincronización adecuada o mueve los datos a un almacenamiento transaccional.

Cuándo usar bisect

Es una buena opción para tablas pequeñas y medianas en memoria, umbrales estáticos, valores de configuración ordenados, catálogos con muchas lecturas, límites temporales e índices ligeros. No conviene usarlo como sustituto de un índice de base de datos en conjuntos grandes y muy cambiantes, ni como cola de prioridad con eliminaciones continuas.

Ejemplo completo: ranking ordenado

from bisect import bisect_left, bisect_right

ranking = []
puntos = []

def agregar(nombre, puntuacion):
    posicion = bisect_right(puntos, puntuacion)
    puntos.insert(posicion, puntuacion)
    ranking.insert(posicion, {"nombre": nombre, "puntos": puntuacion})

def superiores_a(minimo):
    posicion = bisect_left(puntos, minimo)
    return ranking[posicion:]

El ejemplo separa las claves de puntuación de los registros. Esto mejora las búsquedas repetidas, pero exige mantener ambas listas sincronizadas. En producción, oculta la estructura detrás de una clase y prueba listas vacías, duplicados, límites, eliminaciones y actualizaciones.

Buenas prácticas

Documenta el criterio de ordenación, valida los datos de entrada, prueba duplicados y extremos, mide el costo cuando hay muchas inserciones y encapsula las invariantes. También puedes consultar las guías de Academify sobre difflib, fractions, statistics, types y linecache.

Como referencias oficiales, revisa la documentación de bisect y el tutorial de estructuras de datos.

Conclusión

bisect es una solución pequeña, predecible y eficiente para encontrar posiciones en listas ordenadas. Su mayor ventaja es la búsqueda rápida con muy poco código. Cuando la carga tiene muchas lecturas y pocas escrituras, suele ser una excelente opción de la biblioteca estándar. Cuando predominan las actualizaciones, mide el costo real y considera una estructura especializada.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Código y archivos empaquetados con importlib.resources en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    importlib.resources en Python: guía práctica

    Aprende importlib.resources en Python para acceder a archivos empaquetados con seguridad en wheels y aplicaciones instaladas.

    Ler mais

    Tempo de leitura: 5 minutos
    07/08/2026
    Teclado y flujo de datos que representa el procesamiento de varios archivos con fileinput en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    fileinput en Python: lee varios archivos

    Aprende fileinput en Python para leer varios archivos o stdin, rastrear líneas, abrir archivos comprimidos y reescribir contenido con backups.

    Ler mais

    Tempo de leitura: 7 minutos
    07/08/2026
    Editor de código con líneas numeradas que representa el módulo linecache en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    linecache en Python: lee líneas por número

    Aprende linecache en Python para leer líneas por número, administrar la caché, actualizar archivos modificados e integrar traceback y loaders.

    Ler mais

    Tempo de leitura: 7 minutos
    07/08/2026
    Datos binarios que representan serialización interna con marshal en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    marshal en Python: serialización interna

    Aprende marshal en Python para serializar tipos internos, controlar versiones y bloquear objetos de código cuando no sean necesarios.

    Ler mais

    Tempo de leitura: 6 minutos
    06/08/2026
    Monitor con código binario que representa personalización de pickle con copyreg en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    copyreg en Python: personaliza pickle

    Aprende copyreg en Python para registrar funciones de reducción, personalizar pickle y preservar compatibilidad de objetos.

    Ler mais

    Tempo de leitura: 6 minutos
    06/08/2026
    Código Python que representa funciones especializadas con functools.partial
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    functools.partial en Python: guía práctica

    Aprende functools.partial en Python para fijar argumentos, adaptar callbacks y crear funciones especializadas claras.

    Ler mais

    Tempo de leitura: 5 minutos
    06/08/2026