Skip to main content

Lib/heapq.py (part 4)

Source:

cpython 3.14 @ ab2d84fe1023/Lib/heapq.py

This annotation covers merge, selection, and the sift primitives. See modules_heapq3_detail for heappush, heappop, and heapify.

Map

LinesSymbolRole
1-80_siftdownRestore heap invariant upward
81-160_siftupRestore heap invariant downward
161-260heapq.mergeMerge sorted iterables lazily
261-380heapq.nlargestN largest elements
381-500heapq.nsmallestN smallest elements

Reading

_siftdown

# CPython: Lib/heapq.py:198 _siftdown
def _siftdown(heap, startpos, pos):
newitem = heap[pos]
while pos > startpos:
parentpos = (pos - 1) >> 1
parent = heap[parentpos]
if newitem < parent:
heap[pos] = parent
pos = parentpos
continue
break
heap[pos] = newitem

_siftdown "bubbles up" a newly inserted element at pos until the heap invariant holds. The element is compared to its parent (pos-1)//2; if smaller, the parent shifts down and the comparison repeats. This is O(log n).

_siftup

# CPython: Lib/heapq.py:212 _siftup
def _siftup(heap, pos):
endpos = len(heap)
startpos = pos
newitem = heap[pos]
childpos = 2*pos + 1 # left child
while childpos < endpos:
rightpos = childpos + 1
if rightpos < endpos and not heap[childpos] < heap[rightpos]:
childpos = rightpos # use right child if smaller
heap[pos] = heap[childpos]
pos = childpos
childpos = 2*pos + 1
heap[pos] = newitem
_siftdown(heap, startpos, pos)

_siftup "sinks down" the element at pos. At each level it picks the smaller child and moves it up. At the end, the element is placed and _siftdown re-bubbles it (in case the final position is not the bottom). This two-phase approach reduces comparisons.

heapq.merge

# CPython: Lib/heapq.py:312 merge
def merge(*iterables, key=None, reverse=False):
h = []
h_append = h.append
if reverse:
_heappop, _heappush = _heappop_max, _heappush_max
direction = -1
else:
_heappop, _heappush = heappop, heappush
direction = 1
for order, it in enumerate(map(iter, iterables)):
try:
next = it.__next__
h_append([next(), order * direction, next])
except StopIteration:
pass
heapify(h)
while h:
try:
while True:
value, order, next = s = h[0]
yield value
s[0] = next()
_heappush(_heappop(h), h) # replace top
except StopIteration:
_heappop(h)

heapq.merge uses a heap of [current_value, tiebreak_order, next_fn] triples. Each iteration yields the minimum and immediately fetches the next element from the same iterator to replace it. order breaks ties to maintain stable sort order across equal elements.

heapq.nlargest

# CPython: Lib/heapq.py:540 nlargest
def nlargest(n, iterable, key=None):
if n < 0: return []
it = iter(iterable)
if key is None:
result = _nlargest(n, it) # C implementation
return sorted(result, reverse=True)
# key version: maintain a min-heap of (key(x), x)
result = sorted(islice(it, n), reverse=True)
if not result: return result
order = n
top = result[-1]
for elem in it:
k = key(elem)
if k > top:
insort(result, (k, order, elem))
result.pop()
top = result[-1]
order -= 1
return [r[-1] for r in result]

nlargest(n, iterable) uses a size-n min-heap. If the new element is larger than the heap minimum, it replaces it. Final sort gives the top-n in descending order. For small n, this is O(k log n) where k is the iterable length — much better than sorted(iterable)[-n:] which is O(k log k).

gopy notes

_siftdown and _siftup are objects.HeapSiftDown and objects.HeapSiftUp in objects/heapq.go. heapq.merge is pure Python running directly. heapq.nlargest uses module/heapq.NLargest for the no-key case (C extension) and falls back to pure Python with insort for the key case.