As we discussed in the comments, your concerns about copying data when using negative values ββto flip the mini-heap to the max-heap do not matter when you start with an empty heap and add values ββas you go, since this use case is finding the current median of the value stream, negating the values ββas they are added should work fine.
Here's the middle generator that I wrote to just check if it works as I expected:
def running_median(iterable): left_q = [] # heap of smaller-than-median elements, stored negated right_q = [] # heap of larger-than-median elements for value in iterable: if len(left_q) == len(right_q): # push to left_q when they're equal size if len(right_q) > 0 and value > right_q[0]: value = heapq.heapreplace(right_q, value) heapq.heappush(left_q, -value) else: # push to right_q only when it (strictly) smaller if value < -left_q[0]: value = -heapq.heapreplace(left_q, -value) heapq.heappush(right_q, value) # len(left_q) is always >= len(right_q) so we never yield right_q[0] if len(left_q) > len(right_q): yield -left_q[0] else: yield (-left_q[0] + right_q[0]) / 2
The left_q heap stores values ββof less than or equal to-median. Each value is negated when it is pushed, so using the usual mini-heap operations makes it work like the maximum heap. We just need to remember to undo again any value that we pull out of it in order to return to the original sign.
source share