Installation
Install from PyPI:
pip install maxheapx
Import as maxheap
:
import maxheap as heapq
Mirrors 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 maxheapx
Import as maxheap
:
import maxheap as heapq
Use 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-heap
from 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)) # 6
Tuples 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 fetch
Provide <
, <=
, >
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) # C
import 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)) # 5
import 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.