bisect — Algoritmo de bisección de arreglos

Código fuente: Lib/bisect.py


Este módulo brinda soporte para mantener una lista ordenada sin tener que reordenar la lista tras cada nueva inserción. Para listas largas de elementos que tienen operaciones de comparación costosas, será una mejora respecto a la estrategia más habitual. El módulo se llama bisect porque usa un algoritmo de bisección básico para lograr su objetivo. El código fuente puede ser útil como ejemplo del algoritmo en funcionamiento (¡las precondiciones ya están bien de antemano!).

Las siguientes funciones están disponibles:

bisect.bisect_left(a, x, lo=0, hi=len(a))

Ubicar el punto de inserción para x en a para mantener el ordenamiento. Los parámetros lo (inferior) y hi (superior) pueden utilizarse para especificar un subconjunto (subset) de la lista que debería considerarse. Por defecto, se utiliza la lista completa. Si x ya está presente en a, el punto de inserción será antes (a la izquierda de) cualquier elemento existente. El valor de retorno es adecuado para que se utilice como primer parámetro para list.insert(), suponiendo que a ya está ordenada.

El punto de inserción retornado i particiona al arreglo a en dos mitades, tal que all(val < x for val in a[lo:i]) para el lado izquierdo y all(val >= x for val in a[i:hi]) para el derecho.

bisect.bisect_right(a, x, lo=0, hi=len(a))
bisect.bisect(a, x, lo=0, hi=len(a))

Similar a bisect_left(), pero retorna un punto de inserción que viene después (a la derecha de) cualquier entrada de x en a.

El punto de inserción retornado i particiona al arreglo a en dos mitades, tal que all(val <= x for val in a[lo:i]) para el lado izquierdo y all(val > x for val in a[i:hi]) para el derecho.

bisect.insort_left(a, x, lo=0, hi=len(a))

Inserta x en a de forma ordenada. Esto equivale a a.insert(bisect.bisect_left(a, x, lo, hi), x), suponiendo que a ya está ordenada. Tenga presente que la búsqueda O(log n) está dominada por el paso de inserción O(n) lento.

bisect.insort_right(a, x, lo=0, hi=len(a))
bisect.insort(a, x, lo=0, hi=len(a))

Similar a insort_left(), pero inserta x en a después de cualquier entrada x existente.

Ver también

Receta SortedCollection que usa bisección para construir una «clase-colección» con todas las funcionalidades, con métodos de búsqueda directos y con soporte para una función-clave (key-function). Las claves son procesadas de antemano, para ahorrar llamadas innecesarias a la función clave durante las búsquedas.

Búsqueda en listas ordenadas

Las funciones anteriores bisect() son útiles para encontrar puntos de inserción, pero pueden resultar difíciles o engorrosas para tareas de búsqueda habituales. Las cinco funciones que siguen muestran cómo convertirlas en búsquedas estándar para listas ordenadas:

def index(a, x):
    'Locate the leftmost value exactly equal to x'
    i = bisect_left(a, x)
    if i != len(a) and a[i] == x:
        return i
    raise ValueError

def find_lt(a, x):
    'Find rightmost value less than x'
    i = bisect_left(a, x)
    if i:
        return a[i-1]
    raise ValueError

def find_le(a, x):
    'Find rightmost value less than or equal to x'
    i = bisect_right(a, x)
    if i:
        return a[i-1]
    raise ValueError

def find_gt(a, x):
    'Find leftmost value greater than x'
    i = bisect_right(a, x)
    if i != len(a):
        return a[i]
    raise ValueError

def find_ge(a, x):
    'Find leftmost item greater than or equal to x'
    i = bisect_left(a, x)
    if i != len(a):
        return a[i]
    raise ValueError

Otros ejemplos

La función bisect() puede ser útil para búsquedas en tablas numéricas. Este ejemplo utiliza bisect() para buscar una calificación de un examen dada por una letra, basada en un conjunto de marcas numéricas ordenadas: 90 o más es una “A”, de 80 a 89 es una “B”, y así sucesivamente:

>>> def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
...     i = bisect(breakpoints, score)
...     return grades[i]
...
>>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
['F', 'A', 'C', 'C', 'B', 'A', 'A']

A diferencia de la función sorted(), no tiene sentido para las funciones bisect() tener los argumentos key o reversed, porque conduciría a un diseño ineficiente (llamadas sucesivas a funciones de bisección no «recordarían» todas las búsquedas previas con clave).

En su lugar, es mejor buscar en una lista de claves procesadas de antemano para encontrar el índice del registro en cuestión:

>>> data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
>>> data.sort(key=lambda r: r[1])
>>> keys = [r[1] for r in data]         # precomputed list of keys
>>> data[bisect_left(keys, 0)]
('black', 0)
>>> data[bisect_left(keys, 1)]
('blue', 1)
>>> data[bisect_left(keys, 5)]
('red', 5)
>>> data[bisect_left(keys, 8)]
('yellow', 8)