We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Stdlib Mastery step 55 of 55
heapq: a priority queue that is FIFO among equal priorities
Build a priority queue on heapq with stable tie-breaking and cancellation.
class TaskQueue:
def __init__(self) -> None: ...
def push(self, priority: int, name: str) -> None: ...
def remove(self, name: str) -> None: ... # KeyError if not queued
def pop(self) -> str: ... # IndexError when empty
def __len__(self) -> int: ... # number of live tasks
def solve(ops: list[list[object]]) -> list[object]: ...
Lower priority numbers come out first. Among equal priorities, the earliest pushed comes out first. Task names are unique.
solve replays a script of operations and collects results:
| op | effect |
|---|---|
["push", priority, name] |
enqueue |
["pop"] |
append the popped name, or "IndexError" |
["remove", name] |
cancel; append "KeyError" only if it raises |
["len"] |
append len(queue) |
The tuple trap is the whole problem. heappush(heap, (priority, name))
works perfectly until two items share a priority. Tuples compare element by
element: with distinct priorities the comparison stops at the first element
and the payload is never examined. The moment there is a tie, Python falls
through to comparing the payloads — and because these payloads are strings and
therefore orderable, it does not raise. It silently orders alphabetically, so
your FIFO queue becomes an alphabetical one. (With unorderable payloads it
raises TypeError instead, which is the luckier outcome.) One of the tests
pushes zebra, apple and mango at the same priority; the naive heap
returns them in the wrong order and never errors.
The documented fix is a monotonic counter as the tiebreaker, and the typed expression of it is:
@dataclass(order=True, slots=True)
class _Entry:
priority: int
sequence: int
name: str = field(compare=False)
order=True compares the fields as a tuple in declaration order;
field(compare=False) says “tie-break on nothing further” precisely and
self-documentingly, which a bare tuple does not.
Cancellation is lazy deletion. heapq has no remove, because removing
an arbitrary element is $O(n)$ to find plus a full heapify to repair. Record
the cancellation in a set and skip cancelled entries as they surface at the
top. Discard the name from that set once its entry has been dropped, or the
set is the same leak in a different container.
Worth knowing but not tested: 3.14 adds heapify_max / heappush_max /
heappop_max, so a max-heap no longer needs the negate-the-priority trick.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.