Общая очередь приоритетов для Python

Мне нужно использовать приоритетную очередь в моем коде Python и:

  • ищу любые быстрые реализации для очередей с приоритетами
  • оптимально, я бы хотел, чтобы очередь была общей (то есть хорошо работала для любого объекта с указанным оператором сравнения).

В поисках чего-то эффективного я наткнулся на heapq, но:

  • Я ищу что-то более быстрое, чем heapq, которое реализовано на нативном Python, поэтому оно не быстрое.
  • Это выглядит хорошо, но, кажется, указано только для целых чисел. Я предполагаю, что он работает с любыми объектами, у которых есть операторы сравнения, но он не определяет, какие операторы сравнения ему нужны.
  • Обновление: Повторяя сравнение в heapq, я могу либо использовать (priority, object), как предлагает Чарли Мартин, либо просто реализовать __cmp__ для моего объекта.

Ответ 1

Вы можете использовать Queue.PriorityQueue.

Вспомните, что Python не является строго типизированным, поэтому вы можете сохранять все что угодно: просто сделайте кортеж (priority, thing) и все готово.

Ответ 2

Я закончил реализацию оболочки для 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)

Ответ 3

При использовании очереди с приоритетами, клавиша уменьшения является обязательной операцией для многих алгоритмов (Алгоритм Дейкстры, 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

Ответ 4

Вы можете использовать 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

Ответ 5

Я не использовал его, но вы можете попробовать PyHeap. Это написано на C, так что, надеюсь, это достаточно быстро для вас.

Вы уверены, что heapq/PriorityQueue будет недостаточно быстрым? Возможно, стоит пойти с одним из них, чтобы начать, а затем профилировать, чтобы увидеть, действительно ли это ваша производительность.

Ответ 6

Вы посмотрели ссылку "Показать источник" на странице heapq? Пример: немного меньше половины использования кучи со списком (int, char) кортежей в качестве очереди приоритетов.

Ответ 7

Это эффективно и работает для строк или ввода любого типа -:)

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

Справка: http://docs.python.org/library/heapq.html

Ответ 8

У меня есть очередь очереди приоритетов/фибоначчи на 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:)

Ответ 9

Я могу либо использовать (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 приведет к ошибке (и должен!), если вы попытаетесь вставить объект, который не может обработать ваша ключевая функция.

Ответ 10

Если вы хотите упорядочить весь список, а не только верхнее значение, я использовал несколько вариантов этого кода в нескольких проектах, это замена стандартного класса 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"