4 ms·
I agree with your criticisms, the Python version presented in the article is rather baroque. In particular, the use __dict__ seems to have little justification
by olooney 7y ago
I agree with your criticisms, the Python version presented in the article is rather baroque. In particular, the use __dict__ seems to have little justification and serves only to "uglify" the Python.
Here's my version of skew heap in Python 3, edited down to just the essential operations. (Link to slightly fuller version: https://gist.github.com/olooney/97643d07d69d22015ae5bb70c121ca19 https://gist.github.com/olooney/97643d07d69d22015ae5bb70c121...). It seems about as clean as the Haskell version presented, mainly because I used None to represent leaves (which is quite Pythonic.) Relative to the code presented in the article, it also benefits from a clear partition of trees of immutable nodes on the one hand, and a class to represent the mutable heap interface on the other.
from typing import NamedTuple, Any, Optional
class Node(NamedTuple):
"""A single Node in a binary tree."""
value: Any
left: Node
right: Node
def merge(p: Optional[Node], q: Optional[Node]) -> Node:
"""
Implements the critical "merge" operation which is
used by all operations on a SkewHeap. The merge operation
does not mutate either tree but returns a new tree which
contains the least item at the root and is in heap order.
The resulting tree is not necessarily balanced.
"""
if p is None: return q
if q is None: return p
if q.value < p.value:
p, q = q, p
return Node(p.value, merge(p.right, q), p.left)
class SkewHeap:
"""
A SkewHeap is a heap data structure which uses an unbalanced binary tree to
store items. Although no attempt is made to balance the tree, it can be
shown to have amortized O(log n) time complexity for all operations under
the assumption that the items inserted are in random order.
"""
def __init__(self, items=tuple()):
"""
SkewHeap() -> new, empty, skew heap.
SkewHeap(iterable) -> new skew heap initialized from the iterable.
"""
self.root = None
for item in items:
self.push(item)
def push(self, value: Any):
"""Add an item to this heap."""
node = Node(value, None, None)
self.root = merge(self.root, node)
def pop(self):
"""Remove the least item in this heap and return it."""
if self.root is None:
raise ValueError("Cannot pop empty SkewHeap")
else:
value = self.root.value
self.root = merge(self.root.left, self.root.right)
return value
def union(self, other: 'SkewHeap') -> 'SkewHeap':
"""Return a new heap which contains all the items of this and another heap combined."""
ret = SkewHeap()
ret.root = merge(self.root, other.root)
return ret
def __bool__(self) -> bool:
"""Return true iff the heap is non-empty."""
return self.root is not None
def test():
h1 = SkewHeap()
for item in [42, 13, 50, 11, 14, 50, 91, 72, 91]:
h1.push(item)
h2 = SkewHeap([63, 15, 1, 22, 91, 11, 92, 99, 93])
h = h1.union(h2)
while h:
print(h.pop())
Maybe someday I'll read an article comparing two languages where the author's greater familiarity with one language over the other isn't the dominating factor, but it won't be this day.