8.5. bisect — Algorithme de bissection de listes

Nouveau dans la version 2.1.

Code source : Lib/bisect.py


Ce module fournit des outils pour maintenir une liste triée sans avoir à la trier à chaque insertion. Il pourrait être plus rapide que l’approche classique pour de longues listes d’éléments dont les opérations de comparaison sont lourdes. Le module se nomme bisect car il utilise une approche simple par bissection. Si vous voulez un exemple de cet algorithme, le mieux est d’aller lire le code source de ce module (les conditions sur les limites y étant justes !).

Les fonctions suivantes sont fournies :

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

Trouve le point d’insertion de x dans a permettant de conserver l’ordre. Les paramètres lo et hi permettent de limiter les emplacements à vérifier dans la liste, par défaut toute la liste est utilisée. Si x est déjà présent dans a, le point d’insertion proposé sera avant (à gauche) de l’entrée existante. Si a est déjà triée, la valeur renvoyée peut directement être utilisée comme premier paramètre de list.insert().

Le point d’insertion renvoyé, i, coupe la liste a en deux moitiés telles que, pour la moitié de gauche : all(val < x for val in a[lo:i]), et pour la partie de droite : all(val >= x for val in a[i:hi]).

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

Semblable à bisect_left(), mais renvoie un point d’insertion après (à droite) d’une potentielle entrée existante valant x dans a.

Le point d’insertion renvoyé, i, coupe la liste a en deux moitiés telles que, pour la moitié de gauche : all(val <= x for val in a[lo:i]) et pour la moitié de droite : all(val > x for val in a[i:hi]).

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

Insère x dans a en conservant le tri. C’est l’équivalent de a.insert(bisect.bisect_left(a, x, lo, hi), x), tant que a est déjà triée. Gardez en tête que bien que la recherche ne coûte que O(log n), l’insertion coûte O(n).

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

Similaire à insort_left(), mais en insérant x dans a après une potentielle entrée existante égale à x.

Voir aussi

SortedCollection recipe utilise le module bisect pour construire une classe collection exposant des méthodes de recherches naturelles et gèrant une fonction clef. Les clefs sont précalculées pour économiser des appels inutiles à la fonction clef durant les recherches.

8.5.1. Chercher dans des listes triées

Les fonctions bisect() ci-dessus sont utiles pour insérer des éléments, mais peuvent être étranges et peu naturelles à utiliser pour rechercher des éléments. Les cinq fonctions suivantes montrent comment les transformer en recherche plus classique pour les listes triées :

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

8.5.2. Autres Exemples

La fonction bisect() peut être utile pour des recherches dans des tableaux de nombres. Cet exemple utilise bisect() pour rechercher la note (sous forme de lettre) correspondant à un note sous forme de points, en se basant sur une échelle prédéfinie : plus de 90 vaut “A”, de 80 à 89 vaut “B”, etc… :

>>> 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']

Contrairement à la fonction sorted(), ça n’aurait pas de sens pour la fonction bisect() d’avoir un paramètre key ou reversed, qui conduiraient à une utilisation inefficace (des appels successifs à la fonction bisect n’auraient aucun moyen de se « souvenir » des recherches de clef précédentes).

Il est préférable d’utiliser une liste de clefs précalculée pour chercher l’index de l’enregistrement en question :

>>> 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)