Skip to content

Commit 77b882d

Browse files
committed
Add median in a stream using heap-based approach with doctests
1 parent 399d6d7 commit 77b882d

File tree

1 file changed

+44
-37
lines changed

1 file changed

+44
-37
lines changed
Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
"""
2+
Median in a stream using a heap-based approach.
3+
4+
Reference:
5+
https://en.wikipedia.org/wiki/Median#Running_median
6+
"""
7+
18
import heapq
29

310

411
def signum(a: int, b: int) -> int:
512
"""
6-
Compare two integers.
7-
8-
Returns:
9-
1 if a > b
10-
-1 if a < b
11-
0 if a == b
13+
Return 1 if a > b, -1 if a < b, 0 if equal.
1214
"""
1315
if a > b:
1416
return 1
@@ -24,54 +26,51 @@ def call_median(
2426
median: int,
2527
) -> int:
2628
"""
27-
Insert an element into heaps and update the median.
29+
Update heaps and median based on the new element.
30+
31+
Args:
32+
element (int): new element in stream
33+
max_heap (list[int]): max heap (as negative numbers)
34+
min_heap (list[int]): min heap
35+
median (int): current median
36+
37+
Returns:
38+
int: updated median
2839
"""
29-
case = signum(len(max_heap), len(min_heap))
40+
size_diff = signum(len(max_heap), len(min_heap))
3041

31-
if case == 0:
42+
if size_diff == 0:
3243
if element > median:
3344
heapq.heappush(min_heap, element)
3445
median = min_heap[0]
3546
else:
3647
heapq.heappush(max_heap, -element)
3748
median = -max_heap[0]
38-
39-
elif case == 1:
49+
elif size_diff == 1:
4050
if element > median:
4151
heapq.heappush(min_heap, element)
4252
else:
43-
heapq.heappush(min_heap, -heapq.heappop(max_heap))
44-
heapq.heappush(max_heap, -element)
53+
heapq.heappush(min_heap, -heapq.heappushpop(max_heap, -element))
4554
median = (-max_heap[0] + min_heap[0]) // 2
46-
47-
else:
55+
else: # size_diff == -1
4856
if element > median:
49-
heapq.heappush(max_heap, -heapq.heappop(min_heap))
50-
heapq.heappush(min_heap, element)
57+
heapq.heappush(max_heap, -heapq.heappushpop(min_heap, element))
5158
else:
5259
heapq.heappush(max_heap, -element)
5360
median = (-max_heap[0] + min_heap[0]) // 2
5461

5562
return median
5663

5764

58-
def median_in_a_stream(numbers: list[int]) -> list[int]:
65+
def median_in_a_stream(arr: list[int]) -> list[int]:
5966
"""
60-
Find the median after each insertion in a stream of integers.
61-
62-
Uses two heaps and follows the classic running median logic.
63-
64-
Reference:
65-
https://en.wikipedia.org/wiki/Median#Running_median
67+
Return the median after each new element in the stream.
6668
6769
Args:
68-
numbers: List of integers
70+
arr (list[int]): list of integers
6971
7072
Returns:
71-
List of medians after each insertion
72-
73-
Raises:
74-
ValueError: If the input list is empty
73+
list[int]: running medians
7574
7675
>>> median_in_a_stream([20, 14, 13, 16, 17])
7776
[20, 17, 14, 15, 16]
@@ -82,16 +81,24 @@ def median_in_a_stream(numbers: list[int]) -> list[int]:
8281
...
8382
ValueError: Input list must not be empty
8483
"""
85-
if not numbers:
84+
if not arr:
8685
raise ValueError("Input list must not be empty")
8786

88-
max_heap: list[int] = []
89-
min_heap: list[int] = []
90-
median = 0
91-
result: list[int] = []
87+
max_heap: list[int] = [] # left side (as negative numbers)
88+
min_heap: list[int] = [] # right side
89+
median = arr[0]
90+
max_heap.append(-arr[0])
91+
medians: list[int] = [median]
9292

93-
for element in numbers:
93+
for element in arr[1:]:
9494
median = call_median(element, max_heap, min_heap, median)
95-
result.append(median)
95+
medians.append(median)
96+
97+
return medians
98+
9699

97-
return result
100+
if __name__ == "__main__":
101+
n = int(input("Enter number of elements: ").strip())
102+
arr = [int(input().strip()) for _ in range(n)]
103+
result = median_in_a_stream(arr)
104+
print("Running medians:", result)

0 commit comments

Comments
 (0)