From 35148b83323c06c37fa70bf6d32f14facf74bef8 Mon Sep 17 00:00:00 2001 From: phil9l Date: Mon, 12 Oct 2020 19:18:06 +0200 Subject: [PATCH 1/7] Add skew heap data structure. --- data_structures/heap/skew_heap.py | 119 ++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 data_structures/heap/skew_heap.py diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py new file mode 100644 index 000000000000..18fee6fb32a0 --- /dev/null +++ b/data_structures/heap/skew_heap.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +from __future__ import annotations +from typing import TypeVar, Generic, Optional, Iterable, List + +__all__ = ["SkewHeap"] + +T = TypeVar("T") + + +class SkewNode(Generic[T]): + """One node of the skew heap. Contains the value and references to two children.""" + + def __init__(self, value: T) -> None: + self._value: T = value + self.left: Optional[SkewNode[T]] = None + self.right: Optional[SkewNode[T]] = None + + @property + def value(self) -> T: + """Return the value of the node.""" + return self._value + + @staticmethod + def merge( + root1: Optional[SkewNode[T]], root2: Optional[SkewNode[T]] + ) -> Optional[SkewNode[T]]: + """Merge 2 nodes together.""" + if not root1: + return root2 + + if not root2: + return root1 + + if root1.value > root2.value: + root1, root2 = root2, root1 + + result = root1 + temp = root1.right + result.right = root1.left + result.left = SkewNode.merge(temp, root2) + + return result + + +class SkewHeap(Generic[T]): + """ + Data structure that allows to insert a new value and to pop the smallest + values. Both operations take O(logN) time where N is the size of the structure. + - Wiki: https://en.wikipedia.org/wiki/Skew_heap + - Visualisation: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html + + >>> SkewHeap.from_list([2, 3, 1, 5, 1, 7]).to_sorted_list() + [1, 1, 2, 3, 5, 7] + + >>> sh = SkewHeap() + >>> sh.insert(1) + >>> sh.top() + 1 + >>> sh.insert(0) + >>> sh.pop() + 0 + >>> sh.pop() + 1 + >>> sh.top() + Traceback (most recent call last): + ... + AttributeError: Can't get top element for the empty heap. + """ + + def __init__(self) -> None: + self._root: Optional[SkewNode[T]] = None + + def insert(self, value: T) -> None: + """Insert the value into the heap.""" + self._root = SkewNode.merge(self._root, SkewNode(value)) + + def pop(self) -> T: + """Pop the smallest value from the heap and return it.""" + result = self.top() + self._root = SkewNode.merge(self._root.left, self._root.right) + + return result + + def top(self) -> T: + """Return the smallest value from the heap.""" + if not self._root: + raise AttributeError("Can't get top element for the empty heap.") + return self._root.value + + def clear(self): + self._root = None + + @staticmethod + def from_list(data: Iterable[T]) -> SkewHeap[T]: + """Get the sorted list from the heap. Heap will be cleared afterwards.""" + result = SkewHeap() + for item in data: + result.insert(item) + + return result + + def to_sorted_list(self) -> List[T]: + """Returns sorted list containing all the values in the heap.""" + result = [] + while self: + result.append(self.pop()) + + return result + + def __bool__(self) -> bool: + """Check if the heap is not empty.""" + return self._root is not None + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 8bb42d1d6c611d7b3e7211701e3a99adb60d188a Mon Sep 17 00:00:00 2001 From: phil9l Date: Mon, 12 Oct 2020 19:21:39 +0200 Subject: [PATCH 2/7] fixup! Add skew heap data structure. --- data_structures/heap/skew_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 18fee6fb32a0..ef8fe2e891be 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -45,7 +45,7 @@ def merge( class SkewHeap(Generic[T]): """ - Data structure that allows to insert a new value and to pop the smallest + A data structure that allows inserting a new value and to pop the smallest values. Both operations take O(logN) time where N is the size of the structure. - Wiki: https://en.wikipedia.org/wiki/Skew_heap - Visualisation: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html From 441fa13cc7522e6bdd948f7d20b397b9527b7c3b Mon Sep 17 00:00:00 2001 From: phil9l Date: Mon, 12 Oct 2020 19:28:25 +0200 Subject: [PATCH 3/7] fixup! Add skew heap data structure. --- data_structures/heap/skew_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index ef8fe2e891be..5b4d285ba362 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from __future__ import annotations -from typing import TypeVar, Generic, Optional, Iterable, List +from typing import Generic, Iterable, List, Optional, TypeVar __all__ = ["SkewHeap"] From 026933b04dec4c7827cdba59f442db47431ed3f0 Mon Sep 17 00:00:00 2001 From: phil9l Date: Mon, 12 Oct 2020 19:30:40 +0200 Subject: [PATCH 4/7] fixup! Add skew heap data structure. --- data_structures/heap/skew_heap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 5b4d285ba362..cbabe85794ff 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from __future__ import annotations + from typing import Generic, Iterable, List, Optional, TypeVar __all__ = ["SkewHeap"] From c5acb2c9a270a9afd618b8b8fb81c8a865a04b21 Mon Sep 17 00:00:00 2001 From: phil9l Date: Mon, 12 Oct 2020 22:20:51 +0200 Subject: [PATCH 5/7] Add tests. --- data_structures/heap/skew_heap.py | 132 ++++++++++++++++++++++-------- 1 file changed, 100 insertions(+), 32 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index cbabe85794ff..2e2eeee4834e 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -4,13 +4,14 @@ from typing import Generic, Iterable, List, Optional, TypeVar -__all__ = ["SkewHeap"] - T = TypeVar("T") class SkewNode(Generic[T]): - """One node of the skew heap. Contains the value and references to two children.""" + """ + One node of the skew heap. Contains the value and references to + two children. + """ def __init__(self, value: T) -> None: self._value: T = value @@ -47,62 +48,117 @@ def merge( class SkewHeap(Generic[T]): """ A data structure that allows inserting a new value and to pop the smallest - values. Both operations take O(logN) time where N is the size of the structure. - - Wiki: https://en.wikipedia.org/wiki/Skew_heap - - Visualisation: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html + values. Both operations take O(logN) time where N is the size of the + structure. + Wiki: https://en.wikipedia.org/wiki/Skew_heap + Visualisation: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html - >>> SkewHeap.from_list([2, 3, 1, 5, 1, 7]).to_sorted_list() + >>> SkewHeap([2, 3, 1, 5, 1, 7]).to_sorted_list() [1, 1, 2, 3, 5, 7] >>> sh = SkewHeap() - >>> sh.insert(1) - >>> sh.top() - 1 - >>> sh.insert(0) - >>> sh.pop() - 0 >>> sh.pop() - 1 - >>> sh.top() Traceback (most recent call last): ... - AttributeError: Can't get top element for the empty heap. + IndexError: Can't get top element for the empty heap. + + >>> sh.insert(1) + >>> sh.insert(-1) + >>> sh.insert(0) + >>> sh.to_sorted_list() + [-1, 0, 1] """ - def __init__(self) -> None: + def __init__(self, data: Optional[Iterable[T]] = ()) -> None: + """ + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> sh.to_sorted_list() + [1, 3, 3, 7] + """ self._root: Optional[SkewNode[T]] = None + for item in data: + self.insert(item) def insert(self, value: T) -> None: - """Insert the value into the heap.""" + """ + Insert the value into the heap. + + >>> sh = SkewHeap() + >>> sh.insert(3) + >>> sh.insert(1) + >>> sh.insert(3) + >>> sh.insert(7) + >>> sh.to_sorted_list() + [1, 3, 3, 7] + """ self._root = SkewNode.merge(self._root, SkewNode(value)) def pop(self) -> T: - """Pop the smallest value from the heap and return it.""" + """ + Pop the smallest value from the heap and return it. + + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> sh.pop() + 1 + >>> sh.pop() + 3 + >>> sh.pop() + 3 + >>> sh.pop() + 7 + >>> sh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + """ result = self.top() self._root = SkewNode.merge(self._root.left, self._root.right) return result def top(self) -> T: - """Return the smallest value from the heap.""" + """ + Return the smallest value from the heap. + + >>> sh = SkewHeap() + >>> sh.insert(3) + >>> sh.top() + 3 + >>> sh.insert(1) + >>> sh.top() + 1 + >>> sh.insert(3) + >>> sh.top() + 1 + >>> sh.insert(7) + >>> sh.top() + 1 + """ if not self._root: - raise AttributeError("Can't get top element for the empty heap.") + raise IndexError("Can't get top element for the empty heap.") return self._root.value def clear(self): + """ + Clear the heap. + + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> sh.clear() + >>> sh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + """ self._root = None - @staticmethod - def from_list(data: Iterable[T]) -> SkewHeap[T]: - """Get the sorted list from the heap. Heap will be cleared afterwards.""" - result = SkewHeap() - for item in data: - result.insert(item) - - return result - def to_sorted_list(self) -> List[T]: - """Returns sorted list containing all the values in the heap.""" + """ + Returns sorted list containing all the values in the heap. + + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> sh.to_sorted_list() + [1, 3, 3, 7] + """ result = [] while self: result.append(self.pop()) @@ -110,7 +166,19 @@ def to_sorted_list(self) -> List[T]: return result def __bool__(self) -> bool: - """Check if the heap is not empty.""" + """ + Check if the heap is not empty. + + >>> sh = SkewHeap() + >>> bool(sh) + False + >>> sh.insert(1) + >>> bool(sh) + True + >>> sh.clear() + >>> bool(sh) + False + """ return self._root is not None From a3e76eb449dd24c3ace5485da17dee412e1cb23f Mon Sep 17 00:00:00 2001 From: phil9l Date: Wed, 14 Oct 2020 12:03:27 +0200 Subject: [PATCH 6/7] Add __iter__ method. --- data_structures/heap/skew_heap.py | 74 ++++++++++++++++--------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 2e2eeee4834e..700cd90c233b 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Generic, Iterable, List, Optional, TypeVar +from typing import Generic, Iterable, Iterator, List, Optional, TypeVar T = TypeVar("T") @@ -53,7 +53,7 @@ class SkewHeap(Generic[T]): Wiki: https://en.wikipedia.org/wiki/Skew_heap Visualisation: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html - >>> SkewHeap([2, 3, 1, 5, 1, 7]).to_sorted_list() + >>> list(SkewHeap([2, 3, 1, 5, 1, 7])) [1, 1, 2, 3, 5, 7] >>> sh = SkewHeap() @@ -65,20 +65,54 @@ class SkewHeap(Generic[T]): >>> sh.insert(1) >>> sh.insert(-1) >>> sh.insert(0) - >>> sh.to_sorted_list() + >>> list(sh) [-1, 0, 1] """ def __init__(self, data: Optional[Iterable[T]] = ()) -> None: """ >>> sh = SkewHeap([3, 1, 3, 7]) - >>> sh.to_sorted_list() + >>> list(sh) [1, 3, 3, 7] """ self._root: Optional[SkewNode[T]] = None for item in data: self.insert(item) + def __bool__(self) -> bool: + """ + Check if the heap is not empty. + + >>> sh = SkewHeap() + >>> bool(sh) + False + >>> sh.insert(1) + >>> bool(sh) + True + >>> sh.clear() + >>> bool(sh) + False + """ + return self._root is not None + + def __iter__(self) -> Iterator[T]: + """ + Returns sorted list containing all the values in the heap. + + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> list(sh) + [1, 3, 3, 7] + """ + result = [] + while self: + result.append(self.pop()) + + # Pushing items back to the heap not to clear it. + for item in result: + self.insert(item) + + return iter(result) + def insert(self, value: T) -> None: """ Insert the value into the heap. @@ -88,7 +122,7 @@ def insert(self, value: T) -> None: >>> sh.insert(1) >>> sh.insert(3) >>> sh.insert(7) - >>> sh.to_sorted_list() + >>> list(sh) [1, 3, 3, 7] """ self._root = SkewNode.merge(self._root, SkewNode(value)) @@ -151,36 +185,6 @@ def clear(self): """ self._root = None - def to_sorted_list(self) -> List[T]: - """ - Returns sorted list containing all the values in the heap. - - >>> sh = SkewHeap([3, 1, 3, 7]) - >>> sh.to_sorted_list() - [1, 3, 3, 7] - """ - result = [] - while self: - result.append(self.pop()) - - return result - - def __bool__(self) -> bool: - """ - Check if the heap is not empty. - - >>> sh = SkewHeap() - >>> bool(sh) - False - >>> sh.insert(1) - >>> bool(sh) - True - >>> sh.clear() - >>> bool(sh) - False - """ - return self._root is not None - if __name__ == "__main__": import doctest From fc2a9c4837dc8c675f39105e56d415c6f4de114f Mon Sep 17 00:00:00 2001 From: phil9l Date: Wed, 14 Oct 2020 12:10:56 +0200 Subject: [PATCH 7/7] fixup! Add __iter__ method. --- data_structures/heap/skew_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 700cd90c233b..417a383f733e 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Generic, Iterable, Iterator, List, Optional, TypeVar +from typing import Generic, Iterable, Iterator, Optional, TypeVar T = TypeVar("T")