ravl: add ravl tree
Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
parent
4a832e1de8
commit
518ee5b970
2 changed files with 167 additions and 0 deletions
34
ravl.py
Normal file
34
ravl.py
Normal file
|
@ -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
|
133
test_ravl.py
Normal file
133
test_ravl.py
Normal file
|
@ -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
|
Loading…
Reference in a new issue