Priority queues

Sometimes the order things come out is neither "most recent" nor "oldest" but "most important". A priority queue always gives you the smallest value it holds, whatever order the values went in. Urgent orders first, cheapest option first, nearest deadline first.

You could keep a list and call min each time, but min looks at every item, and removing it shifts the rest. The standard library's heapq module keeps a list arranged so that the smallest item is always at position 0 and both adding and removing are fast.

import heapq

prices = []
heapq.heappush(prices, 40)
heapq.heappush(prices, 2.5)
heapq.heappush(prices, 25)
print(prices[0])

print(heapq.heappop(prices))
print(heapq.heappop(prices))
print(heapq.heappop(prices))

heappush adds a value. heappop removes and returns the smallest. prices[0] peeks at the smallest without removing it. Do not use append or sort on a list you are treating as a heap; only the heapq functions keep it arranged correctly.

If you print the list between pushes you will see it is not sorted. It has a special arrangement called a heap that only promises the smallest is first. That is enough.

Priorities with data attached

To attach information to a priority, push tuples. Python compares tuples one position at a time: the first values decide, and only if they are equal does it look at the second values, then the third. So (priority, item) is ordered by priority. Smaller numbers come out first; if you want "higher priority first", use negative numbers.

import heapq

orders = []
heapq.heappush(orders, (3, "bread"))
heapq.heappush(orders, (1, "wedding cake"))
heapq.heappush(orders, (2, "sandwiches"))

while orders:
    priority, item = heapq.heappop(orders)
    print(priority, item)

If two priorities tie, Python compares the second values, so the items must be comparable with each other. Strings and numbers are fine. When they are not, or when you want ties to come out in arrival order, push (priority, count, item) where count goes up by one with every push: the count breaks the tie and the item is never compared.

import heapq

orders = []
count = 0
for priority, item in [(1, "cake"), (1, "rolls"), (0, "bread")]:
    heapq.heappush(orders, (priority, count, item))
    count += 1

while orders:
    priority, count, item = heapq.heappop(orders)
    print(priority, item)

Starting from a list

heapify turns an existing list into a heap in place. nsmallest and nlargest give the k smallest or largest without emptying it.

import heapq

prices = [40, 25, 2.5, 60, 15]
heapq.heapify(prices)
print(prices)
print(prices[0])

print(heapq.nsmallest(2, prices))
print(heapq.nlargest(2, prices))

The second line of output shows that heapify rearranged the list: it is in heap order now, not the order you wrote. If you need the original order kept, heapify a copy.

Which one?

Comes out first Structure Operations
Most recently added stack: a list append, pop
Oldest queue: a deque append, popleft
Smallest priority queue: heapq on a list heappush, heappop

Try it

Next most urgent

orders is a list of (priority, name) tuples, lower number meaning more urgent, in no particular order. Write most_urgent(orders) that returns the name of the most urgent order without changing the list. The list has at least one order and no two orders share a priority.

most_urgent([(3, "bread"), (1, "wedding cake"), (2, "sandwiches")])  ->  "wedding cake"
import heapq

def most_urgent(orders):
    ...

Cheapest k

Write cheapest(prices, k) that returns the k smallest numbers in prices, smallest first. If k is larger than the list, return everything sorted; if k is 0, return an empty list. Do not change prices.

cheapest([40, 25, 2.5, 60, 15], 2)  ->  [2.5, 15]
cheapest([40, 25], 5)               ->  [25, 40]
cheapest([40, 25], 0)               ->  []
import heapq

def cheapest(prices, k):
    ...

Process by priority

orders is a list of (priority, name) tuples, lower meaning more urgent, with no two sharing a priority. Write in_priority_order(orders) that returns the names in the order they should be processed, most urgent first, using a heap: push them all, then pop until empty.

in_priority_order([(3, "bread"), (1, "wedding cake"), (2, "sandwiches")])
    ->  ["wedding cake", "sandwiches", "bread"]
import heapq

def in_priority_order(orders):
    heap = []
    for order in orders:
        ...
    result = []
    while heap:
        ...
    return result

Next: Practice.