Как использовать bisect.insort_left с ключом?
В Doc отсутствует пример... Как вы используете bisect.insort_left)_
на основе ключа?
Попытка вставить на основе ключа.
bisect.insort_left(data, ('brown', 7))
помещает вставку в data[0]
.
Из документов...
bisect.insort_left(
a, x, lo = 0, hi = len (a) )
Вставить x в в отсортированном порядке. Это эквивалентно a.insert(bisect.bisect_left(a, x, lo, hi), x)
, предполагая, что a уже отсортировано. Имейте в виду, что в поиске O (log n) преобладает шаг вставки медленной O (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)
>>>
Я хочу поместить ('brown', 7)
после ('red', 5)
в отсортированный список в data
с помощью bisect.insort_left
. Прямо сейчас bisect.insort_left(data, ('brown', 7))
помещает ('brown', 7)
в data[0]
... потому что я не использую ключи для вставки... docs не показывают делать вставки с помощью клавиш.
Ответы
Ответ 1
По сути, это делает то же самое, что SortedCollection recipe
, о котором упоминается в документации bisect
в разделе " См. Также: в конце", который поддерживает функцию ключа.
То, что делается, - это отдельный список отсортированных keys
который поддерживается параллельно со списком отсортированных data
для повышения производительности (это быстрее, чем создание списка ключей перед каждой вставкой, но хранить его и обновлять его не обязательно). Рецепт ActiveState инкапсулировал это для вас внутри класса, но в приведенном ниже коде они представляют собой просто два независимых независимых списка, поэтому им было бы легче выйти из синхронизации, чем если бы они оба были задержаны. в экземпляре класса рецепт).
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)]
Дополнительный вопрос:
Можно ли использовать bisect.insort_left
?
Нет, вы не можете просто использовать bisect.insort_left()
чтобы сделать это, потому что она не была написана так, чтобы поддерживать функцию ключа - вместо этого она просто сравнивает весь переданный ей элемент для вставки, x
, с один из целых элементов в массиве в выражении if a[mid] < x:
. Вы можете понять, что я имею в виду, посмотрев исходный код модуля bisect
в Lib/bisect.py
.
Вот соответствующая выдержка:
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)
Вы могли бы изменить вышеупомянутое, чтобы принять дополнительный аргумент ключевой функции и использовать его:
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
. . .
... и назовите это так:
my_insort_left(data, ('brown', 7), keyfunc=lambda v: v[1])
На самом деле, если вы собираетесь написать собственную функцию ради большей эффективности за счет ненужной общности, вы можете обойтись без добавления общего аргумента функции ключа и просто жестко закодировать все, чтобы работать так, как нужно с данными формат у вас есть. Это позволит избежать накладных расходов на повторные вызовы ключевой функции при выполнении вставок.
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.
. . .
... вызывается так, не передавая keyfunc:
my_insort_left(data, ('brown', 7))
Ответ 2
Вы можете обернуть свою итерацию в класс, который реализует __getitem__
и __len__
. Это дает вам возможность использовать ключ с bisect_left
. Если вы настроили свой класс на использование итерируемой и ключевой функции в качестве аргументов.
Чтобы расширить его для использования с insort_left
необходимо реализовать метод insert
. Проблема здесь в том, что если вы сделаете это, insort_left
попытается вставить ваш ключевой аргумент в список, содержащий объекты, членом которых является ключ.
Пример понятнее
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")
Посмотрите, как в моем методе insert
я должен был сделать его специфичным для словаря расписания, иначе insort_left
попытается вставить "0359"
куда он должен вставить {"time": "0359"}
?
Обходными путями могут быть создание фиктивного объекта для сравнения, наследование от KeyWrapper
и переопределение insert
или передача некоторой фабричной функции для создания объекта. Ни один из этих способов не является особенно желательным с точки зрения идиоматического питона.
Так что самый простой способ - просто использовать KeyWrapper
с bisect_left
, который возвращает индекс вставки, а затем выполнить вставку самостоятельно. Вы можете легко обернуть это в специальную функцию.
например
bslindex = bisect_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359")
timetable.insert(bslindex, {"time":"0359"})
В этом случае убедитесь, что вы не внедрили insert
, поэтому вы будете немедленно осведомлены, если случайно передадите KeyWrapper
в мутирующую функцию, например insort_left
которая, вероятно, не будет работать правильно.
Чтобы использовать данные вашего примера
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)
Ответ 3
Если ваша цель состоит в том, чтобы сохранить список , отсортированный по ключу, выполняя обычные операции, такие как bisect insert, удалять и обновлять, я думаю, sortedcontainers также должно соответствовать вашим потребностям, и вы избежите вставок O (n).
Ответ 4
Добавьте методы сравнения в ваш класс
Иногда это наименее болезненный способ, особенно если у вас уже есть класс, и вы просто хотите отсортировать его по ключу:
#!/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)
Выход:
black 0
blue 1
red 5
yellow 8
Смотрите также: "Включение" сравнения для классов
Протестировано в Python 3.5.2.