web-dev-qa-db-fra.com

Une file d'attente prioritaire générique pour Python

Je dois utiliser une file d'attente prioritaire dans mon code Python. En cherchant quelque chose d'efficace, je suis tombé sur heapq . Cela a l'air bien, mais ne semble être spécifié que pour les entiers. Je suppose que cela fonctionne avec tous les objets ayant des opérateurs de comparaison, mais cela ne précise pas de quels opérateurs de comparaison il a besoin. 

De plus, heapq semble être implémenté en Python, donc ce n’est pas rapide.

Êtes-vous au courant d'implémentations rapides pour les files d'attente prioritaires en Python? De manière optimale, j'aimerais que la file d'attente soit générique (c'est-à-dire qu'elle fonctionne bien pour tout objet avec un opérateur de comparaison spécifié).

Merci d'avance

Mettre à jour:

Pour la comparaison dans heapq, je peux soit utiliser un (priority, object) comme le suggère Charlie Martin, ou simplement implémenter __cmp__ pour mon objet. 

Je cherche toujours quelque chose de plus rapide que heapq.

40
Eli Bendersky

Um, Queue.PriorityQueue ? Rappelez-vous que Python n'est pas fortement typé, vous pouvez donc sauvegarder ce que vous voulez: créez un tuple (priorité, chose) et vous êtes prêt.

34
Charlie Martin

J'ai fini par implémenter un wrapper pour heapq, en ajoutant un dict pour maintenir les éléments de la file d'attente uniques. Le résultat devrait être assez efficace pour tous les opérateurs:

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's 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)
17
Eli Bendersky

Lorsque vous utilisez une file d'attente prioritaire, une touche de diminution est une opération indispensable pour de nombreux algorithmes (algorithme de Dijkstra, A *, OPTICS), je me demande pourquoi la file d'attente prioritaire intégrée de Python ne la prend pas en charge. Aucune des autres réponses n'offre une solution prenant en charge cette fonctionnalité.

Une file d’attente prioritaire prenant également en charge l’opération de réduction de clé est this l’implémentation de Daniel Stutzbach a parfaitement fonctionné pour moi avec 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
8
Guy s

Je ne l'ai pas utilisé, mais vous pouvez essayer PyHeap . C'est écrit en C alors espérons que c'est assez rapide pour vous. 

Êtes-vous certain que heapq/PriorityQueue ne sera pas assez rapide? Cela vaut peut-être la peine de commencer par l’un d’eux, puis de faire un profil pour voir si c’est vraiment votre performance.

7
Kiv

Vous pouvez utiliser heapq pour des éléments non entiers (tuples)

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

Avez-vous consulté le lien "Show Source" sur la page heapq? Voici un exemple un peu moins de la moitié de l'utilisation d'un segment de mémoire avec une liste de tuples (int, char) comme file d'attente prioritaire.

6
Harper Shelby

Ceci est efficace et fonctionne aussi bien pour les chaînes que pour les entrées - :)

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

Référence:http://docs.python.org/library/heapq.html

2
codersofthedark

J'ai un tas de file d'attente/fibonacci prioritaire à https://pypi.python.org/pypi/fibonacci-heap-mod

Ce n'est pas rapide (grande constante c sur delete-min, qui est O (c * logn)). Mais trouver-min, insérer, diminuer-touche et fusionner sont tous O(1) - IOW, c'est paresseux.

Si c'est trop lent sur CPython, vous pouvez essayer Pypy, Nuitka ou même CPython + Numba :)

1
user1277476

Je peux soit utiliser un (priority, object) comme le suggère Charlie Martin, ou simplement implémenter __cmp__ pour mon objet.

Si vous souhaitez que les objets insérés soient hiérarchisés par une règle spécifique, il m'a été très utile d'écrire une simple sous-classe de PriorityQueue qui accepte une fonction-clé. Vous n'aurez pas à insérer manuellement les __plicots (priority, object) et la manipulation vous semblera plus naturelle.

Démo du comportement souhaité :

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

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

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

Évidemment, appeler put va (et devrait!) Générer une erreur si vous essayez d'insérer un objet que votre fonction de clé ne peut pas traiter.

0
timgeb