From 518ee5b97043d69d50d0429984fb8fdaf5058640 Mon Sep 17 00:00:00 2001 From: Matej Focko Date: Sun, 30 Jan 2022 15:03:59 +0100 Subject: [PATCH] ravl: add ravl tree Signed-off-by: Matej Focko --- ravl.py | 34 +++++++++++++ test_ravl.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 ravl.py create mode 100644 test_ravl.py diff --git a/ravl.py b/ravl.py new file mode 100644 index 0000000..7b15f98 --- /dev/null +++ b/ravl.py @@ -0,0 +1,34 @@ +from wavl import WAVLTree +from node import Node, Comparable + +import logging +from typing import TypeVar, Optional + +logger = logging.getLogger(__name__) +T = TypeVar("T", bound=Comparable) + + +class RAVLTree(WAVLTree[T]): + def is_correct_node( + self, node: Optional[Node[T]], recursive: bool = True + ) -> bool: + if not node: + return True + + if not all(child_rank > 0 for child_rank in Node.differences(node)): + return False + + return not recursive or ( + self.is_correct_node(node.left) + and self.is_correct_node(node.right) + ) + + # region DeleteRebalance + + def _delete_rebalance( + self, node: Optional[Node[T]], parent: Optional[Node[T]] + ) -> None: + # There is no rebalance after delete in rAVL tree + pass + + # endregion DeleteRebalance diff --git a/test_ravl.py b/test_ravl.py new file mode 100644 index 0000000..e61f9f6 --- /dev/null +++ b/test_ravl.py @@ -0,0 +1,133 @@ +from ravl import RAVLTree + +import hypothesis +import hypothesis.strategies as st +import logging +import pytest +import random + +logger = logging.getLogger(__name__) + + +def test_empty(): + tree = RAVLTree() + + assert tree.root is None + assert tree.is_correct + + +def test_one_node(): + tree = RAVLTree() + tree.insert(1) + + assert tree.root is not None + assert 1 == tree.root.value + assert 0 == tree.root.rank + assert tree.root.left is None + assert tree.root.right is None + + assert tree.is_correct + + +@pytest.mark.parametrize("values", [[1, 2], [1, 2, 0]]) +def test_no_rebalance_needed(values): + tree = RAVLTree() + + for value in values: + tree.insert(value) + assert tree.is_correct + + +def test_three_nodes_rebalanced(): + tree = RAVLTree() + + for value in (1, 2, 3): + print(tree) + tree.insert(value) + + assert tree.is_correct + + +def test_bigger_tree(): + tree = RAVLTree() + + for i in range(50): + tree.insert(i) + assert tree.is_correct + + +def test_bigger_tree_reversed(): + tree = RAVLTree() + + for i in range(50): + tree.insert(-i) + assert tree.is_correct + + +def test_promote(): + tree = RAVLTree() + + for value in (0, 1, -1): + tree.insert(value) + assert tree.is_correct + + +@pytest.mark.parametrize( + "values", + [ + [0, 1, -2, -1, -3], + [0, 1, -2, -1, -3, -4, 4, 9, 7], + [0, 1, -2, -1, -3, -4, 4, 9, 7, 5, -5, 8], + ], +) +def test_rotate(values): + tree = RAVLTree() + + for value in values: + tree.insert(value) + assert tree.is_correct + + +@hypothesis.settings(max_examples=1000, deadline=None) +@hypothesis.given(values=st.sets(st.integers())) +def test_insert_random(values): + tree = RAVLTree() + + for value in values: + tree.insert(value) + assert tree.is_correct + assert tree.search(value) is not None + + +@st.composite +def delete_strategy(draw): + values = list(draw(st.sets(st.integers()))) + delete_order = values.copy() + random.shuffle(delete_order) + return (values, delete_order) + + +@hypothesis.settings(max_examples=10000, deadline=None) +@hypothesis.given(config=delete_strategy()) +def test_delete_random(config): + values, delete_order = config + + tree = RAVLTree() + + for value in values: + tree.insert(value) + + for value in delete_order: + before = str(tree) + tree.delete(value) + after = str(tree) + + try: + assert tree.is_correct + except AssertionError: + logger.info( + f"[FAIL] Delete {value} from {values} in order {delete_order}" + ) + logger.info(f"Before:\n{before}") + logger.info(f"After:\n{after}") + raise