Installation
Install from PyPI:
pip install maxheapxImport as maxheap:
import maxheap as heapqMirrors the stdlib heapq API, but max-first. Optional C accelerator with pure-Python fallback, fully typed (PEP 561), rigorously tested, zero runtime dependencies. Supports Python 3.9–3.13.
Install from PyPI:
pip install maxheapxImport as maxheap:
import maxheap as heapqUse the functional API on a plain list, just like heapq.
import maxheap as heapq
# functional API on a plain list
h = []
heapq.heappush(h, 10)
heapq.heappush(h, 3)
heapq.heappush(h, 42)
print(heapq.peek(h)) # 42
print(heapq.heappop(h)) # 42
print(h) # still a valid max-heapfrom maxheap import MaxHeap
h = MaxHeap([3, 1, 6, 5, 2, 4])
h.push(7)
print(h.peek()) # 7
print(h.pop()) # 7
print(len(h)) # 6Tuples compare lexicographically. Put the priority first to get highest-priority first.
import maxheap as heapq
# (priority, value) — highest priority first
tasks = []
heapq.heappush(tasks, (10, "render"))
heapq.heappush(tasks, (5, "fetch"))
heapq.heappush(tasks, (42, "compile"))
while tasks:
prio, name = heapq.heappop(tasks)
print(prio, name) # 42 compile, 10 render, 5 fetchProvide <, <=, > so items are totally ordered.
import maxheap as heapq
# Custom objects must be totally ordered (define <, <=, >)
class Job:
__slots__ = ("deadline", "name")
def __init__(self, deadline: int, name: str):
self.deadline = deadline
self.name = name
# "larger" deadline should come out first
def __lt__(self, other): return self.deadline < other.deadline
def __le__(self, other): return self.deadline <= other.deadline
def __gt__(self, other): return self.deadline > other.deadline
h = []
heapq.heappush(h, Job(10, "A"))
heapq.heappush(h, Job(3, "B"))
heapq.heappush(h, Job(42, "C"))
print(heapq.heappop(h).name) # Cimport maxheap as heapq
def top_k(iterable, k):
h = []
for x in iterable:
if len(h) < k:
heapq.heappush(h, x)
else:
# Keep only the largest k overall
if x > h[0]:
heapq.heapreplace(h, x)
# Drain as descending order
return [heapq.heappop(h) for _ in range(len(h))]
print(top_k([7, 2, 9, 4, 1, 8], 3)) # [9, 8, 7]import maxheap as heapq
def kth_largest(nums, k):
nums = list(nums)
heapq.heapify(nums)
for _ in range(k - 1):
heapq.heappop(nums)
return heapq.heappop(nums)
print(kth_largest([3, 2, 1, 5, 6, 4], 2)) # 5import maxheap as heapq
def max_sliding_window(nums, k):
h = []
res = []
# seed
for i in range(k):
heapq.heappush(h, (nums[i], i))
res.append(heapq.peek(h)[0])
for i in range(k, len(nums)):
heapq.heappush(h, (nums[i], i))
# drop indices that left window
while h and heapq.peek(h)[1] <= i - k:
heapq.heappop(h)
res.append(heapq.peek(h)[0])
return res
print(max_sliding_window([1,3,-1,-3,5,3,6,7], 3)) # [3,3,5,5,6,7]Microbenchmarks comparing maxheap to the typical heapq “max-heap via negation” workaround show consistent wins for common operations on our machines. Results vary by hardware, Python version, and data distribution — measure on your workload.
"""
Microbenchmarks comparing maxheap (max-first) vs heapq+negation baseline.
Run directly with: python bench_maxheap.py
"""
import time, random, statistics, platform
import maxheap as mh
import heapq as q
def run(n=100_000, repeat=5, seed=1337):
rnd = random.Random(seed)
xs = [rnd.randint(0, 10**6) for _ in range(n)]
def timeit(fn):
ts = []
for _ in range(repeat):
t0 = time.perf_counter()
fn()
ts.append(time.perf_counter() - t0)
return {"min": min(ts), "median": statistics.median(ts), "mean": statistics.fmean(ts)}
# heappush
r_push_mh = timeit(lambda: [mh.heappush([], x) for x in xs])
r_push_qn = timeit(lambda: [q.heappush([], -x) for x in xs])
# heapify
r_hf_mh = timeit(lambda: mh.heapify(list(xs)))
r_hf_qn = timeit(lambda: q.heapify([-x for x in xs]))
# heapify+drain
def drain_mh():
h = list(xs); mh.heapify(h)
for _ in range(len(h)):
mh.heappop(h)
def drain_qn():
h = [-x for x in xs]; q.heapify(h)
for _ in range(len(h)):
q.heappop(h)
r_dr_mh = timeit(drain_mh)
r_dr_qn = timeit(drain_qn)
def pct(better, baseline): return (baseline - better) / baseline * 100.0
print(f"Python {platform.python_version()} on {platform.system()} {platform.machine()}\n")
print(f"{'operation':<22} {'median (mh)':>12} {'median (hq+neg)':>16} {'speedup':>9}")
print("-" * 65)
print(f"{'heappush':<22} {r_push_mh['median']:.6f}s {r_push_qn['median']:.6f}s {pct(r_push_mh['median'], r_push_qn['median']):+6.1f}%")
print(f"{'heapify':<22} {r_hf_mh['median']:.6f}s {r_hf_qn['median']:.6f}s {pct(r_hf_mh['median'], r_hf_qn['median']):+6.1f}%")
print(f"{'heapify+drain':<22} {r_dr_mh['median']:.6f}s {r_dr_qn['median']:.6f}s {pct(r_dr_mh['median'], r_dr_qn['median']):+6.1f}%")
if __name__ == "__main__":
run()Transform list x into a max-heap, in place. O(n).
Push item onto heap. O(log n).
Pop and return the largest item. O(log n). Raises IndexError on empty heaps.
Push then pop in one step. Returns the previous max if item is smaller; otherwise returns item. O(log n).
Pop max then push item atomically. O(log n). Raises IndexError on empty heaps.
Return the largest element without removing it. O(1). Raises IndexError on empty heaps.
Validate the max-heap property (helpful for debugging/tests).
A convenience wrapper around the functional API.
from maxheap import MaxHeap
h = MaxHeap([3, 1, 6, 5, 2, 4])
h.push(10)
top = h.pushpop(7) # push, then pop and return the largest
old = h.replace(4) # pop current max, push 4
print(top, old, h.peek())push(item), pop(), peek(), replace(item), pushpop(item)heap() returns the underlying list (mutating it affects the heap).__len__ / __bool__ behave as expected.Items must be totally ordered (<, <=, >). For complex types, implement rich comparisons or wrap values with a key object.
heapify is O(n); push/pop/replace are O(log n); peek is O(1).
heappop, peek, and heapreplace raise IndexError on empty heaps.
Hot paths (heapify, heappush, heappop, heappushpop, heapreplace) are C-accelerated when available; otherwise a pure-Python fallback is used automatically.
Verify that the C extension is active:
import maxheap, sys
print("C extension loaded:", getattr(maxheap, "_HAVE_CEXT", None))
print("heapify impl:", maxheap.heapify.__module__)
print("python:", sys.version)heapq?heapq is a min-heap. You can simulate a max-heap by negating numbers, but that’s awkward for non-numeric types and adds overhead. maxheap is max-first by design, with a C accelerator for speed.
Python 3.9–3.13 (typed; zero runtime deps). The wheel bundles a compiled extension where available; otherwise it builds from sdist.
Yes. The heap is just a Python list. Mutating it directly can break the invariant; use the API or call heapify after manual edits.