python 35 lines · 8 steps

Running median with two heaps

A pair of complementary heaps keeps the median available in constant time as values stream in.

Explained by highlit
1import heapq
2 
3 
4class MovingMedian:
5 def __init__(self):
6 self._low = []
7 self._high = []
8 
9 def add(self, value):
10 if not self._low or value <= -self._low[0]:
11 heapq.heappush(self._low, -value)
12 else:
13 heapq.heappush(self._high, value)
14 self._rebalance()
15 
16 def _rebalance(self):
17 if len(self._low) > len(self._high) + 1:
18 heapq.heappush(self._high, -heapq.heappop(self._low))
19 elif len(self._high) > len(self._low):
20 heapq.heappush(self._low, -heapq.heappop(self._high))
21 
22 @property
23 def median(self):
24 if not self._low:
25 raise ValueError("no data points yet")
26 if len(self._low) > len(self._high):
27 return float(-self._low[0])
28 return (-self._low[0] + self._high[0]) / 2.0
29 
30 
31def stream_medians(values):
32 tracker = MovingMedian()
33 for value in values:
34 tracker.add(value)
35 yield tracker.median
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Two heaps split around the middle let you read the median from the roots in constant time.
  2. 2Python only ships a min-heap, so negating values turns `_low` into a max-heap.
  3. 3Enforcing a size invariant after every insert is what keeps the median at the heap roots.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Running median with two heaps — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code