Welcome, guest | Sign In | My Account | Store | Cart

Thread-safe priority queue using Queue.Queue

Python, 38 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import Queue

class PriorityQueue(Queue.Queue):
    def _put(self, item):
        data, priority = item
        self._insort_right((priority, data))
        
    def _get(self):
        return self.queue.pop(0)[1]

    def _insort_right(self, x):
        """Insert item x in list, and keep it sorted assuming a is sorted.
        
        If x is already in list, insert it to the right of the rightmost x.       
        """
        a = self.queue
        lo = 0        
        hi = len(a)
        
        while lo < hi:
            mid = (lo+hi)/2
            if x[0] < a[mid][0]: hi = mid
            else: lo = mid+1
        a.insert(lo, x)

def test():
    pq = PriorityQueue()

    pq.put(('b', 1))
    pq.put(('a', 1))
    pq.put(('c', 1))
    pq.put(('z', 0))
    pq.put(('d', 2))

    while not pq.empty():
        print pq.get(),   
    
test() # prints z b a c d

10 comments

Lee Harr 22 years, 2 months ago  # | flag

pop or get? Queue.Queue does not have a pop() method. It does however have a get() method.

Lee Harr 22 years, 2 months ago  # | flag

output. For me, this outputs: z b a c d

output. typo corrected.

pop method. I don't quite understand what you mean. the pop() call in _get-calls the internal queue-data structure, which is python list.

Mark Moraes 21 years, 10 months ago  # | flag

using bisect.insort_right(). Would using bisect.insort_right() cause problems? i.e.

def _put(self, item):
    bisect.insort_right(self.queue, item)

seems to work nicely.

I also found a 'top' method useful:

def top(self):
    "non-destructively return smallest element in pqueue"
    try:
        x = self.queue[0]
    except IndexError:
        x = None
    return x
Chris Perkins 21 years, 6 months ago  # | flag

bisect.insort_right will not work. Using bisect.insort_right as you suggest would not work.

Assuming item is a tuple of (priority, data), then it would be inserted amongst other items in the queue with the same priority, but sorted by the value of its data, rather than by its arrival time.

You may be able to hack around this with something like:

self.queue.insert(bisect.bisect_right(self.queue, (priority, sys.maxint)), (priority, data))

if the data are all integers.

greg klanderman 21 years, 3 months ago  # | flag

bisection. why bother with bisection if you're just going to use an O(n) insert?

Thomas Ahle 16 years, 11 months ago  # | flag

Why not use a heapqueue. Like this:

from Queue import Queue
from heapq import heappush, heappop

class PriorityQueue(Queue):
    # Initialize the queue representation
    def _init(self, maxsize):
        self.maxsize = maxsize
        self.queue = []

    # Put a new item in the queue
    def _put(self, item):
        return heappush(self.queue, item)

    # Get an item from the queue
    def _get(self):
        return heappop(self.queue)

if __name__ == "__main__":
    q = PriorityQueue()
    q.put((2,"a"))
    q.put((0,"b"))
    q.put((1,"c"))
    q.put((2,"d"))
    q.put((1,"e"))
    while not q.empty():
        print q.get()

prints b, c, e, a, d

Giles Brown 16 years, 10 months ago  # | flag

Using bisect module. I don't really understand the maths of heapq, but it doesn't seem to maintain the existing ordering. In fact if you use the example from the published 2nd edition cookbook, if you insert a series of items with the same time.time() value they get ordered according to their (undecorated) item value which is not so good.

And I'm a bit bothered by not re-using the bisect algorithm, so here is a version that doesn't sort on item value, but does use the bisect module.

from Queue import Queue
from bisect import bisect_left

class PriorityQueue(Queue):

    def _init(self, maxsize):
        self.maxsize = maxsize
        # Python 2.5 uses collections.deque, but we can't because
        # we need insert(pos, item) for our priority stuff
        self.queue = []

    def put(self, item, priority=0, block=True, timeout=None):
        """Puts an item onto the queue with a numeric priority (default is zero).

        Note that we are "shadowing" the original Queue.Queue put() method here.
        """
        Queue.put(self, (priority, item), block, timeout)

    def _put(self, item):
        """Override of the Queue._put to support prioritisation."""
        # Priorities must be integers!
        priority = int(item[0])

        # Using a tuple (priority+1,) finds us the correct insertion
        # position to maintain the existing ordering.
        self.queue.insert(bisect_left(self.queue, (priority+1,)), item)

    def _get(self):
        """Override of Queue._get().  Strips the priority."""
        return self.queue.pop(0)[1]
David Arnold 15 years, 11 months ago  # | flag

Priority Queue Semantics. Maintaining the existing ordering isn't usually what a priority queue does. If you have several objects with the same priority, it doesn't matter what order you address them in. (After all, they have the /same/ priority.) ER-style priority, like your example, is a different strategy (items that have the same value for their priority don't technically have the same 'priority'). Often, applications that require priority queues (like implementations of Dijkstra's algorithm, or A* search) would much rather have the performance boost of a heap versus the maintenance of first-come ordering. (heap is O(logN) to insert, list insort is O(NlogN))

It's a trade-off of performance versus a specific semantic. I am curious: what situation did you create this PriorityQueue for that demanded first-come ordering?

Created by simo salminen on Sun, 11 Nov 2001 (PSF)
Python recipes (4591)
simo salminen's recipes (1)
Python Cookbook Edition 1 (103)

Required Modules

Other Information and Tasks