web-dev-qa-db-fra.com

Comment utiliser bisect.insort_left avec une clé?

Les doc manquent d'exemple ... Comment utilisez-vous bisect.insort_left)_ Sur la base d'une clé?

Essayer d'insérer en fonction de la clé.

bisect.insort_left(data, ('brown', 7))

met insert à data[0].

De documents ...

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

Insérez x dans a dans l'ordre trié. Cela équivaut à a.insert(bisect.bisect_left(a, x, lo, hi), x) en supposant que a est déjà trié. Gardez à l'esprit que la recherche O (log n) est dominée par l'étape d'insertion lente O(n)).

Exemple d'utilisation:

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

Je veux mettre ('brown', 7) Après ('red', 5) Sur la liste triée dans data en utilisant bisect.insort_left. En ce moment bisect.insort_left(data, ('brown', 7)) met ('brown', 7) À data[0] ... parce que je n'utilise pas les touches pour faire de l'insertion ... les documents ne montrent pas de faire des insertions en utilisant les touches .

31
Merlin

Cela fait essentiellement la même chose que le SortedCollection recipe fait que le bisect documentation mentionne dans le Voir également: section à la fin qui prend en charge une fonction clé.

Ce qui est fait est une liste keys triée séparée est maintenue en parallèle avec la liste data triée pour améliorer les performances (c'est plus rapide que de créer la liste des clés avant chaque insertion, mais de la conserver et de la mettre à jour) n'est pas strictement requis). La recette ActiveState l'encapsule pour vous au sein d'une classe, mais dans le code ci-dessous, ce ne sont que deux listes indépendantes distinctes qui sont transmises (il serait donc plus facile pour elles de se désynchroniser que si elles étaient toutes deux tenues dans une instance de la classe de la recette).

from bisect import bisect_left

def insert(seq, keys, item, keyfunc=lambda v: v):
    """Insert an item into a sorted list using a separate corresponding
       sorted keys list and a keyfunc() to extract the key from each item.

    Based on insert() method in SortedCollection recipe:
    http://code.activestate.com/recipes/577197-sortedcollection/
    """
    k = keyfunc(item)  # Get key.
    i = bisect_left(keys, k)  # Determine where to insert item.
    keys.insert(i, k)  # Insert key of item to keys list.
    seq.insert(i, item)  # Insert the item itself in the corresponding place.

# Initialize the sorted data and keys lists.
data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
data.sort(key=lambda r: r[1]) # Sort data by key value
keys = [r[1] for r in data]   # Initialize keys list
print(data)  # -> [('black', 0), ('blue', 1), ('red', 5), ('yellow', 8)]

insert(data, keys, ('brown', 7), keyfunc=lambda x: x[1])
print(data)  # -> [('black', 0), ('blue', 1), ('red', 5), ('brown', 7), ('yellow', 8)]

Question complémentaire:
Peut-on utiliser bisect.insort_left?

Non, vous ne pouvez pas simplement utiliser la fonction bisect.insort_left() pour ce faire car elle n'a pas été écrite d'une manière qui prend en charge une fonction de clé - au lieu de cela, elle compare simplement l'élément entier qui lui est passé pour l'insérer, x, avec l'un des éléments entiers du tableau dans son instruction if a[mid] < x:. Vous pouvez voir ce que je veux dire en regardant la source du module bisect dans Lib/bisect.py .

Voici l'extrait pertinent:

def insort_left(a, x, lo=0, hi=None):
    """Insert item x in list a, and keep it sorted assuming a is sorted.

    If x is already in a, insert it to the left of the leftmost x.

    Optional args lo (default 0) and hi (default len(a)) bound the
    slice of a to be searched.
    """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if a[mid] < x: lo = mid+1
        else: hi = mid
    a.insert(lo, x)

Vous pouvez modifier ce qui précède pour accepter un argument clé-fonction facultatif et l'utiliser:

def my_insort_left(a, x, lo=0, hi=None, keyfunc=lambda v: v):
    x_key = keyfunc(x)  # Get comparison value.
    . . .
        if keyfunc(a[mid]) < x_key: # Compare key values.
            lo = mid+1
    . . .

... et appelez-le comme ceci:

my_insort_left(data, ('brown', 7), keyfunc=lambda v: v[1])

En fait, si vous allez écrire une fonction personnalisée, pour plus d'efficacité au détriment d'une généralité inutile, vous pouvez vous passer de l'ajout d'un argument de fonction clé générique et simplement coder en dur tout pour fonctionner de la manière requise avec les données format que vous avez. Cela évitera la surcharge des appels répétés à une fonction clé lors des insertions.

def my_insort_left(a, x, lo=0, hi=None):
    x_key = x[1]   # Key on second element of each item in sequence.
    . . .
        if a[mid][1] < x_key: lo = mid+1  # Compare second element to key.
    . . .

... appelé ainsi sans passer keyfunc:

my_insort_left(data, ('brown', 7))
15
martineau

Vous pouvez envelopper votre itérable dans une classe qui implémente __getitem__ et __len__. Cela vous permet d'utiliser une clé avec bisect_left. Si vous configurez votre classe pour prendre l'itérable et une fonction clé comme arguments.

Pour étendre ceci afin qu'il soit utilisable avec insort_left il est nécessaire d'implémenter la méthode insert. Le problème ici est que si vous faites cela, c'est que insort_left essaiera d'insérer votre argument clé dans la liste contenant les objets dont la clé est membre.

Un exemple est plus clair

from bisect import bisect_left, insort_left


class KeyWrapper:
    def __init__(self, iterable, key):
        self.it = iterable
        self.key = key

    def __getitem__(self, i):
        return self.key(self.it[i])

    def __len__(self):
        return len(self.it)

    def insert(self, index, item):
        print('asked to insert %s at index%d' % (item, index))
        self.it.insert(index, {"time":item})

timetable = [{"time": "0150"}, {"time": "0250"}, {"time": "0350"}, {"time": "0450"}, {"time": "0550"}, {"time": "0650"}, {"time": "0750"}]

bslindex = bisect_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359")

islindex = insort_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359")

Voyez comment, dans ma méthode insert, j'ai dû le rendre spécifique au dictionnaire horaire autrement insort_left essaierait d'insérer "0359" où il doit insérer {"time": "0359"}?

Pour contourner ce problème, vous pouvez construire un objet factice pour la comparaison, hériter de KeyWrapper et remplacer insert ou passer une sorte de fonction d'usine pour créer l'objet. Aucun de ces moyens n'est particulièrement souhaitable d'un point de vue idiomatique python.

Donc, la façon la plus simple est d'utiliser simplement le KeyWrapper avec bisect_left, qui vous renvoie l'index d'insertion, puis effectuez l'insertion vous-même. Vous pouvez facilement envelopper cela dans une fonction dédiée.

par exemple.

bslindex = bisect_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359")
timetable.insert(bslindex, {"time":"0359"})

Dans ce cas, assurez-vous de ne pas implémenter insert, vous serez donc immédiatement averti si vous passez accidentellement un KeyWrapper à une fonction de mutation comme insort_left qui ne ferait probablement pas la bonne chose.

Pour utiliser vos données d'exemple

from bisect import bisect_left


class KeyWrapper:
    def __init__(self, iterable, key):
        self.it = iterable
        self.key = key

    def __getitem__(self, i):
        return self.key(self.it[i])

    def __len__(self):
        return len(self.it)

data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
data.sort(key=lambda c: c[1])

newcol = ('brown', 7)

bslindex = bisect_left(KeyWrapper(data, key=lambda c: c[1]), newcol[1])
data.insert(bslindex, newcol)

print(data)
7
Paul Rooney

Si votre objectif est de conserver une liste triée par clé , en effectuant des opérations habituelles comme bissect insert , supprimez et mettez à jour, je pense que sortedcontainers devrait également répondre à vos besoins, et vous éviterez O(n) inserts.

4
mmj

Ajoutez des méthodes de comparaison à votre classe

Parfois, c'est le moyen le moins douloureux, surtout si vous avez déjà une classe et que vous souhaitez simplement trier par clé:

#!/usr/bin/env python3

import bisect
import functools

@functools.total_ordering
class MyData:
    def __init__(self, color, number):
        self.color = color
        self.number = number
    def __lt__(self, other):
        return self.number < other .number
    def __str__(self):
        return '{} {}'.format(self.color, self.number)

mydatas = [
    MyData('red', 5),
    MyData('blue', 1),
    MyData('yellow', 8),
    MyData('black', 0),
]
mydatas_sorted = []
for mydata in mydatas:
    bisect.insort(mydatas_sorted, mydata)
for mydata in mydatas_sorted:
    print(mydata)

Production:

black 0
blue 1
red 5
yellow 8

Voir aussi: "Activation" de la comparaison pour les classes

Testé en Python 3.5.2.