Created
January 28, 2020 02:03
-
-
Save marccarre/577a55850998da02af3d4b7b98152cf4 to your computer and use it in GitHub Desktop.
Min and max heaps in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations # To allow "MinHeap.push -> MinHeap:" | |
from typing import Generic, List, Optional, TypeVar | |
from heapq import heapify, heappop, heappush, heapreplace | |
T = TypeVar('T') | |
class MinHeap(Generic[T]): | |
''' | |
MinHeap provides a nicer API around heapq's functionality. | |
As it is a minimum heap, the first element of the heap is always the | |
smallest. | |
>>> h = MinHeap([3, 1, 4, 2]) | |
>>> h[0] | |
1 | |
>>> h.peek() | |
1 | |
>>> h.push(5) # N.B.: the array isn't always fully sorted. | |
[1, 2, 4, 3, 5] | |
>>> h.pop() | |
1 | |
>>> h.pop() | |
2 | |
>>> h.pop() | |
3 | |
>>> h.push(3).push(2) | |
[2, 3, 4, 5] | |
>>> h.replace(1) | |
2 | |
>>> h | |
[1, 3, 4, 5] | |
''' | |
def __init__(self, array: Optional[List[T]] = None): | |
if array is None: | |
array = [] | |
heapify(array) | |
self.h = array | |
def push(self, x: T) -> MinHeap: | |
heappush(self.h, x) | |
return self # To allow chaining operations. | |
def peek(self) -> T: | |
return self.h[0] | |
def pop(self) -> T: | |
return heappop(self.h) | |
def replace(self, x: T) -> T: | |
return heapreplace(self.h, x) | |
def __getitem__(self, i) -> T: | |
return self.h[i] | |
def __len__(self) -> int: | |
return len(self.h) | |
def __str__(self) -> str: | |
return str(self.h) | |
def __repr__(self) -> str: | |
return str(self.h) | |
class Reverse(Generic[T]): | |
''' | |
Wrap around the provided object, reversing the comparison operators. | |
>>> 1 < 2 | |
True | |
>>> Reverse(1) < Reverse(2) | |
False | |
>>> Reverse(2) < Reverse(1) | |
True | |
>>> Reverse(1) <= Reverse(2) | |
False | |
>>> Reverse(2) <= Reverse(1) | |
True | |
>>> Reverse(2) <= Reverse(2) | |
True | |
>>> Reverse(1) == Reverse(1) | |
True | |
>>> Reverse(2) > Reverse(1) | |
False | |
>>> Reverse(1) > Reverse(2) | |
True | |
>>> Reverse(2) >= Reverse(1) | |
False | |
>>> Reverse(1) >= Reverse(2) | |
True | |
>>> Reverse(1) | |
1 | |
''' | |
def __init__(self, x: T) -> None: | |
self.x = x | |
def __lt__(self, other: Reverse) -> bool: | |
return other.x.__lt__(self.x) | |
def __le__(self, other: Reverse) -> bool: | |
return other.x.__le__(self.x) | |
def __eq__(self, other) -> bool: | |
return self.x == other.x | |
def __ne__(self, other: Reverse) -> bool: | |
return other.x.__ne__(self.x) | |
def __ge__(self, other: Reverse) -> bool: | |
return other.x.__ge__(self.x) | |
def __gt__(self, other: Reverse) -> bool: | |
return other.x.__gt__(self.x) | |
def __str__(self): | |
return str(self.x) | |
def __repr__(self): | |
return str(self.x) | |
class MaxHeap(MinHeap): | |
''' | |
MaxHeap provides an implement of a maximum-heap, as heapq does not provide | |
it. As it is a maximum heap, the first element of the heap is always the | |
largest. It achieves this by wrapping around elements with Reverse, | |
which reverses the comparison operations used by heapq. | |
>>> h = MaxHeap([3, 1, 4, 2]) | |
>>> h[0] | |
4 | |
>>> h.peek() | |
4 | |
>>> h.push(5) # N.B.: the array isn't always fully sorted. | |
[5, 4, 3, 1, 2] | |
>>> h.pop() | |
5 | |
>>> h.pop() | |
4 | |
>>> h.pop() | |
3 | |
>>> h.pop() | |
2 | |
>>> h.push(3).push(2).push(4) | |
[4, 3, 2, 1] | |
>>> h.replace(1) | |
4 | |
>>> h | |
[3, 1, 2, 1] | |
''' | |
def __init__(self, array: Optional[List[T]] = None): | |
if array is not None: | |
array = [Reverse(x) for x in array] # Wrap with Reverse. | |
super().__init__(array) | |
def push(self, x: T) -> MaxHeap: | |
super().push(Reverse(x)) | |
return self | |
def peek(self) -> T: | |
return super().peek().x | |
def pop(self) -> T: | |
return super().pop().x | |
def replace(self, x: T) -> T: | |
return super().replace(Reverse(x)).x | |
if __name__ == '__main__': | |
import doctest | |
doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment