Ответ 1
Вы можете использовать Queue.PriorityQueue.
Вспомните, что Python не является строго типизированным, поэтому вы можете сохранять все что угодно: просто сделайте кортеж (priority, thing)
и все готово.
Мне нужно использовать приоритетную очередь в моем коде Python и:
В поисках чего-то эффективного я наткнулся на heapq, но:
heapq
, которое реализовано на нативном Python, поэтому оно не быстрое.heapq
, я могу либо использовать (priority, object)
, как предлагает Чарли Мартин, либо просто реализовать __cmp__
для моего объекта.Вы можете использовать Queue.PriorityQueue.
Вспомните, что Python не является строго типизированным, поэтому вы можете сохранять все что угодно: просто сделайте кортеж (priority, thing)
и все готово.
Я закончил реализацию оболочки для heapq
, добавив dict для поддержки уникальных элементов очереди. Результат должен быть достаточно эффективным для всех операторов:
class PriorityQueueSet(object):
"""
Combined priority queue and set data structure.
Acts like a priority queue, except that its items are guaranteed to be
unique. Provides O(1) membership test, O(log N) insertion and O(log N)
removal of the smallest item.
Important: the items of this data structure must be both comparable and
hashable (i.e. must implement __cmp__ and __hash__). This is true of
Python built-in objects, but you should implement those methods if you
want to use the data structure for custom objects.
"""
def __init__(self, items=[]):
"""
Create a new PriorityQueueSet.
Arguments:
items (list): An initial item list - it can be unsorted and
non-unique. The data structure will be created in O(N).
"""
self.set = dict((item, True) for item in items)
self.heap = self.set.keys()
heapq.heapify(self.heap)
def has_item(self, item):
"""Check if ``item`` exists in the queue."""
return item in self.set
def pop_smallest(self):
"""Remove and return the smallest item from the queue."""
smallest = heapq.heappop(self.heap)
del self.set[smallest]
return smallest
def add(self, item):
"""Add ``item`` to the queue if doesn't already exist."""
if item not in self.set:
self.set[item] = True
heapq.heappush(self.heap, item)
При использовании очереди с приоритетами, клавиша уменьшения является обязательной операцией для многих алгоритмов (Алгоритм Дейкстры, A *, OPTICS), мне интересно, почему встроенная очередь приоритетов Python не поддерживает ее. Ни один из других ответов не предоставляет решение, которое поддерживает эту функцию.
Очередь приоритетов, которая также поддерживает операцию уменьшения ключа, - эта реализация Даниеля Штутцбаха отлично работала для меня с Python 3.5.
from heapdict import heapdict
hd = heapdict()
hd["two"] = 2
hd["one"] = 1
obj = hd.popitem()
print("object:",obj[0])
print("priority:",obj[1])
# object: one
# priority: 1
Вы можете использовать heapq для нецелых элементов (кортежей)
from heapq import *
heap = []
data = [(10,"ten"), (3,"three"), (5,"five"), (7,"seven"), (9, "nine"), (2,"two")]
for item in data:
heappush(heap, item)
sorted = []
while heap:
sorted.append(heappop(heap))
print sorted
data.sort()
print data == sorted
Я не использовал его, но вы можете попробовать PyHeap. Это написано на C, так что, надеюсь, это достаточно быстро для вас.
Вы уверены, что heapq/PriorityQueue будет недостаточно быстрым? Возможно, стоит пойти с одним из них, чтобы начать, а затем профилировать, чтобы увидеть, действительно ли это ваша производительность.
Вы посмотрели ссылку "Показать источник" на странице heapq? Пример: немного меньше половины использования кучи со списком (int, char) кортежей в качестве очереди приоритетов.
Это эффективно и работает для строк или ввода любого типа -:)
pq = [] # list of entries arranged in a heap
entry_finder = {} # mapping of tasks to entries
REMOVED = '<removed-task>' # placeholder for a removed task
counter = itertools.count() # unique sequence count
def add_task(task, priority=0):
'Add a new task or update the priority of an existing task'
if task in entry_finder:
remove_task(task)
count = next(counter)
entry = [priority, count, task]
entry_finder[task] = entry
heappush(pq, entry)
def remove_task(task):
'Mark an existing task as REMOVED. Raise KeyError if not found.'
entry = entry_finder.pop(task)
entry[-1] = REMOVED
def pop_task():
'Remove and return the lowest priority task. Raise KeyError if empty.'
while pq:
priority, count, task = heappop(pq)
if task is not REMOVED:
del entry_finder[task]
return task
raise KeyError('pop from an empty priority queue')
У меня есть очередь очереди приоритетов/фибоначчи на https://pypi.python.org/pypi/fibonacci-heap-mod
Это не быстро (большая константа c на delete-min, которая является O (c * logn)). Но find-min, insert, reduce-key и merge - все O (1) - IOW, это лениво.
Если он слишком медленный на CPython, вы можете попробовать Pypy, Nuitka или даже CPython + Numba:)
Я могу либо использовать
(priority, object)
, как предлагает Чарли Мартин, либо просто реализовать__cmp__
для моего объекта.
Если вы хотите, чтобы вставленные объекты были приоритетными по определенному правилу, мне было очень полезно написать простой подкласс PriorityQueue
, который принимает ключевую функцию. Вам не нужно вставлять кортежи (priority, object)
вручную, и обработка становится более естественной.
Демонстрация желаемого поведения:
>>> h = KeyHeap(sum)
>>> h.put([-1,1])
>>> h.put((-1,-2,-3))
>>> h.put({100})
>>> h.put([1,2,3])
>>> h.get()
(-1, -2, -3)
>>> h.get()
[-1, 1]
>>> h.get()
[1, 2, 3]
>>> h.get()
set([100])
>>> h.empty()
True
>>>
>>> k = KeyHeap(len)
>>> k.put('hello')
>>> k.put('stackoverflow')
>>> k.put('!')
>>> k.get()
'!'
>>> k.get()
'hello'
>>> k.get()
'stackoverflow'
Код Python 2
from Queue import PriorityQueue
class KeyHeap(PriorityQueue):
def __init__(self, key, maxsize=0):
PriorityQueue.__init__(self, maxsize)
self.key = key
def put(self, x):
PriorityQueue.put(self, (self.key(x), x))
def get(self):
return PriorityQueue.get(self)[1]
Код Python 3
from queue import PriorityQueue
class KeyHeap(PriorityQueue):
def __init__(self, key, maxsize=0):
super().__init__(maxsize)
self.key = key
def put(self, x):
super().put((self.key(x), x))
def get(self):
return super().get()[1]
Очевидно, что вызов put
приведет к ошибке (и должен!), если вы попытаетесь вставить объект, который не может обработать ваша ключевая функция.
Если вы хотите упорядочить весь список, а не только верхнее значение, я использовал несколько вариантов этого кода в нескольких проектах, это замена стандартного класса list
с похожим API:
import bisect
class OrderedList(list):
"""Keep a list sorted as you append or extend it
An ordered list, this sorts items from smallest to largest using key, so
if you want MaxQueue like functionality use negative values: .pop(-1) and
if you want MinQueue like functionality use positive values: .pop(0)
"""
def __init__(self, iterable=None, key=None):
if key:
self.key = key
self._keys = []
super(OrderedList, self).__init__()
if iterable:
for x in iterable:
self.append(x)
def key(self, x):
return x
def append(self, x):
k = self.key(x)
# https://docs.python.org/3/library/bisect.html#bisect.bisect_right
i = bisect.bisect_right(self._keys, k)
if i is None:
super(OrderedList, self).append((self.key(x), x))
self._keys.append(k)
else:
super(OrderedList, self).insert(i, (self.key(x), x))
self._keys.insert(i, k)
def extend(self, iterable):
for x in iterable:
self.append(x)
def remove(self, x):
k = self.key(x)
self._keys.remove(k)
super(OrderedList, self).remove((k, x))
def pop(self, i=-1):
self._keys.pop(i)
return super(OrderedList, self).pop(i)[-1]
def clear(self):
super(OrderedList, self).clear()
self._keys.clear()
def __iter__(self):
for x in super(OrderedList, self).__iter__():
yield x[-1]
def __getitem__(self, i):
return super(OrderedList, self).__getitem__(i)[-1]
def insert(self, i, x):
raise NotImplementedError()
def __setitem__(self, x):
raise NotImplementedError()
def reverse(self):
raise NotImplementedError()
def sort(self):
raise NotImplementedError()
По умолчанию он может обрабатывать кортежи типа (priority, value)
, но вы также можете настроить его следующим образом:
class Val(object):
def __init__(self, priority, val):
self.priority = priority
self.val = val
h = OrderedList(key=lambda x: x.priority)
h.append(Val(100, "foo"))
h.append(Val(10, "bar"))
h.append(Val(200, "che"))
print(h[0].val) # "bar"
print(h[-1].val) # "che"