From f5c8dd2a4dee48183959b51e7c6166b817f0113d Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 9 Nov 2024 13:36:23 +0300 Subject: [PATCH 01/22] feat(Treap): implement classes TreapNode and Treap (including methods: preorder, postorder, insert_node, remove, split, merge, find. --- project/treap.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 project/treap.py diff --git a/project/treap.py b/project/treap.py new file mode 100644 index 00000000..ecd12cdc --- /dev/null +++ b/project/treap.py @@ -0,0 +1,58 @@ +from collections.abc import MutableMapping + + +class TreapNode: + key = None + value = None + left = None + right = None + + def __init__(self, key, value): + self.key = key + self.value = value + self.left = None + self.right = None + + +class Treap(MutableMapping): + def __init__(self): + self.root = None + + def insert_node(self, key, value): + + new_node = TreapNode.__init__(key, value) + if self.root is None: + self.root = new_node + return + else: + left, right = self.split(self.root, key - 1) + self.root = self.merge(left, new_node) + self.root = self.merge(self.root, right) + + + def remove(self, root: TreapNode, key): + + + def split(self, root: TreapNode, key): + if root is None: + return None, None + elif root.key < key: + (left, right) = self.split(root.right, key) + root.right = left + return root, right + else: + (left, right) = self.split(root.left, key) + root.left = right + return left, root + + def merge(self, left: TreapNode, right: TreapNode): + if left is None: + return right + if right is None: + return left + elif left.value > right.value: + left.right = self.merge(left.right, right) + return left + else: + right.left = self.merge(right.left, left) + return right From ff2d1c66694909d65d2e55fe8de3cfcf3d77f716 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 9 Nov 2024 15:02:12 +0300 Subject: [PATCH 02/22] fix(Treap): fix structure of class Treap and add new methods. --- project/treap.py | 66 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/project/treap.py b/project/treap.py index ecd12cdc..d24b9a9d 100644 --- a/project/treap.py +++ b/project/treap.py @@ -18,8 +18,51 @@ class Treap(MutableMapping): def __init__(self): self.root = None - def insert_node(self, key, value): + def __getitem__(self, key): + node = self.find(self.root, key) + if node is not None: + return node.value + raise KeyError(f"Key {key} not found in Treap") + + def __setitem__(self, key, value): + self.insert_node(key, value) + + def __delitem__(self, key): + self.root = self.remove(self.root, key) + + def __iter__(self): + return self.inorder(self.root) + + def __len__(self): + return self.count_nodes(self.root) + + def inorder(self, node): + if node is not None: + yield from self.inorder(node.left) + yield node.key + yield from self.inorder(node.right) + + def count_nodes(self, node): + if node is None: + return 0 + return 1 + self.count_nodes(node.left) + self.count_nodes(node.right) + + def preorder(self, root: TreapNode): + if root is None: + return + print(root.key, end=" ") + self.preorder(root.left) + self.preorder(root.right) + + def postorder(self, root: TreapNode): + if root is None: + return + self.preorder(root.left) + self.preorder(root.right) + print(root.key, end=" ") + + def insert_node(self, key, value): new_node = TreapNode.__init__(key, value) if self.root is None: self.root = new_node @@ -29,9 +72,16 @@ def insert_node(self, key, value): self.root = self.merge(left, new_node) self.root = self.merge(self.root, right) - def remove(self, root: TreapNode, key): - + if root is None: + return None + elif root.key < key: + root.right = self.remove(root.right, key) + elif root.key > key: + root.left = self.remove(root.left, key) + else: + root = self.merge(root.left, root.right) + return root def split(self, root: TreapNode, key): if root is None: @@ -56,3 +106,13 @@ def merge(self, left: TreapNode, right: TreapNode): else: right.left = self.merge(right.left, left) return right + + def find(self, root: TreapNode, key): + if root is None: + return None + elif root.key < key: + root.right = self.find(root.right, key) + elif root.key > key: + root.left = self.find(root.left, key) + else: + return root From 873a9ccbe6564a10ad60cf8ada6550bc29fb4c9a Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 9 Nov 2024 16:39:07 +0300 Subject: [PATCH 03/22] feat(test): treap fixed & tests added --- project/treap.py | 33 +++++++++----------- tests/test_basic.py | 74 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/project/treap.py b/project/treap.py index d24b9a9d..44e78fdb 100644 --- a/project/treap.py +++ b/project/treap.py @@ -48,27 +48,25 @@ def count_nodes(self, node): return 0 return 1 + self.count_nodes(node.left) + self.count_nodes(node.right) - def preorder(self, root: TreapNode): - if root is None: - return - print(root.key, end=" ") - self.preorder(root.left) - self.preorder(root.right) + def preorder(self, node: TreapNode): + if node is not None: + yield node.key + yield from self.preorder(node.left) + yield from self.preorder(node.right) - def postorder(self, root: TreapNode): - if root is None: - return - self.preorder(root.left) - self.preorder(root.right) - print(root.key, end=" ") + def postorder(self, node: TreapNode): + if node is not None: + yield from self.postorder(node.left) + yield from self.postorder(node.right) + yield node.key def insert_node(self, key, value): - new_node = TreapNode.__init__(key, value) + new_node = TreapNode(key, value) if self.root is None: self.root = new_node return else: - left, right = self.split(self.root, key - 1) + left, right = self.split(self.root, key) self.root = self.merge(left, new_node) self.root = self.merge(self.root, right) @@ -111,8 +109,7 @@ def find(self, root: TreapNode, key): if root is None: return None elif root.key < key: - root.right = self.find(root.right, key) + return self.find(root.right, key) elif root.key > key: - root.left = self.find(root.left, key) - else: - return root + return self.find(root.left, key) + return root \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py index 4811167b..60578106 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,18 +1,74 @@ import pytest -import project # on import will print something from __init__ file +from project.treap import Treap, TreapNode -def setup_module(module): - print("basic setup module") +@pytest.fixture +def empty_tree(): + return Treap() +@pytest.fixture +def filled_tree(): + tree = Treap() + # Добавляем узлы, чтобы значения value у родителя всегда были больше значений детей + tree.insert_node(10, 100) + tree.insert_node(5, 80) + tree.insert_node(15, 60) + tree.insert_node(7, 90) + tree.insert_node(12, 70) + tree.insert_node(18, 50) + return tree -def teardown_module(module): - print("basic teardown module") +def test_inorder_key_bst(filled_tree): + expected_list = [5, 7, 10, 12, 15, 18] + assert expected_list == list(filled_tree.inorder(filled_tree.root)) +def test_insert_and_get_item(empty_tree): + # Проверка вставки и получения значения + empty_tree[10] = 100 + empty_tree[5] = 80 + assert empty_tree[10] == 100, "Value for key 10 should be 100" + assert empty_tree[5] == 80, "Value for key 5 should be 80" -def test_1(): - assert 1 + 1 == 2 +def test_get_nonexistent_key(empty_tree): + with pytest.raises(KeyError): + _ = empty_tree[100] +def test_delete_item(filled_tree): + # Проверка удаления узла + filled_tree.__delitem__(5) + with pytest.raises(KeyError): + _ = filled_tree[5] + assert filled_tree[10] == 100, "Root node with key 10 should still exist" + +def test_inorder_traversal(filled_tree): + # Проверка обхода inorder + expected_inorder = [5, 7, 10, 12, 15, 18] + assert list(filled_tree.inorder(filled_tree.root)) == expected_inorder, "Inorder traversal does not match expected output" + +def test_preorder_traversal(filled_tree): + expected_preorder = [10, 7, 5, 12, 15, 18] + assert list(filled_tree.preorder(filled_tree.root)) == expected_preorder + +def test_postorder_traversal(filled_tree): + expected_postorder = [5, 7, 18, 15, 12, 10] + assert list(filled_tree.postorder(filled_tree.root)) == expected_postorder + + +def test_len_function(filled_tree, empty_tree): + # Проверка количества узлов в дереве + assert len(filled_tree) == 6, "Tree should have 6 nodes" + assert len(empty_tree) == 0, "Empty tree should have 0 nodes" + +def test_heap_property(filled_tree): + # Проверка свойства кучи (value родителя > value детей) для Treap + def check_heap_property(node): + if node is None: + return True + if node.left and node.left.value > node.value: + return False + if node.right and node.right.value > node.value: + return False + return check_heap_property(node.left) and check_heap_property(node.right) + + assert check_heap_property(filled_tree.root), "Heap property (parent value > children values) should hold for all nodes" -def test_2(): - assert "1" + "1" == "11" From 90b544032245a7edfe2b17726b5562b40e78588d Mon Sep 17 00:00:00 2001 From: Shvorob Date: Sun, 10 Nov 2024 20:25:36 +0300 Subject: [PATCH 04/22] feat(ci): ci test running added --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..b67d8fc8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Run Tests + +on: + push: + branches: + - task_6 + pull_request: + branches: + - task_6 + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests + run: | + python ./scripts/run_tests.py From 034101869f475dfa02328001df526a7a5adcb589 Mon Sep 17 00:00:00 2001 From: Shvorob Date: Sun, 10 Nov 2024 20:37:45 +0300 Subject: [PATCH 05/22] fix(codestyle): beautify code --- project/treap.py | 3 +-- tests/test_basic.py | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/project/treap.py b/project/treap.py index 44e78fdb..73d48089 100644 --- a/project/treap.py +++ b/project/treap.py @@ -36,7 +36,6 @@ def __iter__(self): def __len__(self): return self.count_nodes(self.root) - def inorder(self, node): if node is not None: yield from self.inorder(node.left) @@ -112,4 +111,4 @@ def find(self, root: TreapNode, key): return self.find(root.right, key) elif root.key > key: return self.find(root.left, key) - return root \ No newline at end of file + return root diff --git a/tests/test_basic.py b/tests/test_basic.py index 60578106..1a7a531c 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -2,10 +2,12 @@ from project.treap import Treap, TreapNode + @pytest.fixture def empty_tree(): return Treap() + @pytest.fixture def filled_tree(): tree = Treap() @@ -18,10 +20,12 @@ def filled_tree(): tree.insert_node(18, 50) return tree + def test_inorder_key_bst(filled_tree): expected_list = [5, 7, 10, 12, 15, 18] assert expected_list == list(filled_tree.inorder(filled_tree.root)) + def test_insert_and_get_item(empty_tree): # Проверка вставки и получения значения empty_tree[10] = 100 @@ -29,10 +33,12 @@ def test_insert_and_get_item(empty_tree): assert empty_tree[10] == 100, "Value for key 10 should be 100" assert empty_tree[5] == 80, "Value for key 5 should be 80" + def test_get_nonexistent_key(empty_tree): with pytest.raises(KeyError): _ = empty_tree[100] + def test_delete_item(filled_tree): # Проверка удаления узла filled_tree.__delitem__(5) @@ -40,15 +46,20 @@ def test_delete_item(filled_tree): _ = filled_tree[5] assert filled_tree[10] == 100, "Root node with key 10 should still exist" + def test_inorder_traversal(filled_tree): # Проверка обхода inorder expected_inorder = [5, 7, 10, 12, 15, 18] - assert list(filled_tree.inorder(filled_tree.root)) == expected_inorder, "Inorder traversal does not match expected output" + assert list( + filled_tree.inorder(filled_tree.root) + ) == expected_inorder, "Inorder traversal does not match expected output" + def test_preorder_traversal(filled_tree): expected_preorder = [10, 7, 5, 12, 15, 18] assert list(filled_tree.preorder(filled_tree.root)) == expected_preorder + def test_postorder_traversal(filled_tree): expected_postorder = [5, 7, 18, 15, 12, 10] assert list(filled_tree.postorder(filled_tree.root)) == expected_postorder @@ -59,6 +70,7 @@ def test_len_function(filled_tree, empty_tree): assert len(filled_tree) == 6, "Tree should have 6 nodes" assert len(empty_tree) == 0, "Empty tree should have 0 nodes" + def test_heap_property(filled_tree): # Проверка свойства кучи (value родителя > value детей) для Treap def check_heap_property(node): @@ -70,5 +82,6 @@ def check_heap_property(node): return False return check_heap_property(node.left) and check_heap_property(node.right) - assert check_heap_property(filled_tree.root), "Heap property (parent value > children values) should hold for all nodes" - + assert check_heap_property( + filled_tree.root + ), "Heap property (parent value > children values) should hold for all nodes" From 4860dc199c524e81426df98fb11f67b30ea2ab0d Mon Sep 17 00:00:00 2001 From: Shvorob Date: Sun, 10 Nov 2024 20:47:20 +0300 Subject: [PATCH 06/22] fix(codestyle): pre-commit beautify --- tests/test_basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 1a7a531c..df7eab1e 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -50,9 +50,9 @@ def test_delete_item(filled_tree): def test_inorder_traversal(filled_tree): # Проверка обхода inorder expected_inorder = [5, 7, 10, 12, 15, 18] - assert list( - filled_tree.inorder(filled_tree.root) - ) == expected_inorder, "Inorder traversal does not match expected output" + assert ( + list(filled_tree.inorder(filled_tree.root)) == expected_inorder + ), "Inorder traversal does not match expected output" def test_preorder_traversal(filled_tree): From cc7f61a441d311c0849fd747bb180963cd2a32a0 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 16:24:05 +0300 Subject: [PATCH 07/22] fix(ci): ci started on push and pr --- .github/workflows/ci.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b67d8fc8..16f22f81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,7 @@ name: Run Tests on: - push: - branches: - - task_6 - pull_request: - branches: - - task_6 - - main + [ push, pull_request ] jobs: test: From da22cc0c09e7aeb8b571c13e5b5573a210343047 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 16:25:52 +0300 Subject: [PATCH 08/22] feat(ci): mypy added + ci test --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16f22f81..f44469a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,19 +8,27 @@ jobs: runs-on: ubuntu-latest steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.8' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Run tests - run: | - python ./scripts/run_tests.py + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install mypy + run: | + python -m pip install mypy + + - name: Run mypy + run: | + mypy . + + - name: Run tests + run: | + python ./scripts/run_tests.py \ No newline at end of file From 6fb56ba4e0a3124a9b402f975955fdcc9629533a Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 16:33:14 +0300 Subject: [PATCH 09/22] test(ci): extra space in the end of file --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f44469a3..f735b2df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,4 +31,4 @@ jobs: - name: Run tests run: | - python ./scripts/run_tests.py \ No newline at end of file + python ./scripts/run_tests.py From 003cb09fbdc54333c34ee38d859bfe5e00539b04 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 16:44:42 +0300 Subject: [PATCH 10/22] chore(treap): beautify for mypy --- project/treap.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/project/treap.py b/project/treap.py index 73d48089..696a1dfc 100644 --- a/project/treap.py +++ b/project/treap.py @@ -1,4 +1,5 @@ from collections.abc import MutableMapping +from typing import Optional class TreapNode: @@ -47,13 +48,13 @@ def count_nodes(self, node): return 0 return 1 + self.count_nodes(node.left) + self.count_nodes(node.right) - def preorder(self, node: TreapNode): + def preorder(self, node: Optional[TreapNode]): if node is not None: yield node.key yield from self.preorder(node.left) yield from self.preorder(node.right) - def postorder(self, node: TreapNode): + def postorder(self, node: Optional[TreapNode]): if node is not None: yield from self.postorder(node.left) yield from self.postorder(node.right) @@ -69,7 +70,7 @@ def insert_node(self, key, value): self.root = self.merge(left, new_node) self.root = self.merge(self.root, right) - def remove(self, root: TreapNode, key): + def remove(self, root: Optional[TreapNode], key): if root is None: return None elif root.key < key: @@ -80,7 +81,7 @@ def remove(self, root: TreapNode, key): root = self.merge(root.left, root.right) return root - def split(self, root: TreapNode, key): + def split(self, root: Optional[TreapNode], key): if root is None: return None, None elif root.key < key: @@ -92,7 +93,7 @@ def split(self, root: TreapNode, key): root.left = right return left, root - def merge(self, left: TreapNode, right: TreapNode): + def merge(self, left: Optional[TreapNode], right: Optional[TreapNode]): if left is None: return right if right is None: @@ -104,7 +105,7 @@ def merge(self, left: TreapNode, right: TreapNode): right.left = self.merge(right.left, left) return right - def find(self, root: TreapNode, key): + def find(self, root: Optional[TreapNode], key): if root is None: return None elif root.key < key: From 1fcfe341e6c6d58cf831072dbdeac723a6423d0d Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 16:45:06 +0300 Subject: [PATCH 11/22] fix(ci): mypy.ini for ignoring venv added --- mypy.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..2f2beca7 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +exclude = venv/ \ No newline at end of file From fe0410190798c51f9de36a7b280ae356722cdc88 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 16:47:59 +0300 Subject: [PATCH 12/22] fix(treap): elif removed --- project/treap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/treap.py b/project/treap.py index 696a1dfc..f401eb48 100644 --- a/project/treap.py +++ b/project/treap.py @@ -98,7 +98,7 @@ def merge(self, left: Optional[TreapNode], right: Optional[TreapNode]): return right if right is None: return left - elif left.value > right.value: + if left.value > right.value: left.right = self.merge(left.right, right) return left else: From 3e32ffd7fb76f98e136aed8d8592ccbdb2c6b52d Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 17:05:54 +0300 Subject: [PATCH 13/22] fix(treap): fixes for mypy type checker --- project/treap.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/project/treap.py b/project/treap.py index f401eb48..75ed83eb 100644 --- a/project/treap.py +++ b/project/treap.py @@ -96,14 +96,21 @@ def split(self, root: Optional[TreapNode], key): def merge(self, left: Optional[TreapNode], right: Optional[TreapNode]): if left is None: return right - if right is None: - return left - if left.value > right.value: - left.right = self.merge(left.right, right) + elif right is None: return left else: - right.left = self.merge(right.left, left) - return right + if (left.value is None) | (right.value is None): + return None + + assert left.value is not None + assert right.value is not None + + if left.value > right.value: + left.right = self.merge(left.right, right) + return left + else: + right.left = self.merge(right.left, left) + return right def find(self, root: Optional[TreapNode], key): if root is None: From 0346e289c1efd75371dc02ba0704f855f4d8835d Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 17:07:20 +0300 Subject: [PATCH 14/22] fix(ci): end of file --- mypy.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index 2f2beca7..624cd195 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,2 +1,2 @@ [mypy] -exclude = venv/ \ No newline at end of file +exclude = venv/ From 912a842fee59a52917ece130b7bf24a680078916 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 17:16:50 +0300 Subject: [PATCH 15/22] feat(test): test for operator in added --- tests/test_basic.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index df7eab1e..de9bee83 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -85,3 +85,15 @@ def check_heap_property(node): assert check_heap_property( filled_tree.root ), "Heap property (parent value > children values) should hold for all nodes" + + +def test_contains_operator(filled_tree, empty_tree): + assert 10 not in empty_tree, "Empty tree should not contain any key" + + assert 10 in filled_tree, "Key 10 should be in the filled tree" + assert 5 in filled_tree, "Key 5 should be in the filled tree" + assert 15 in filled_tree, "Key 15 should be in the filled tree" + assert 7 in filled_tree, "Key 7 should be in the filled tree" + assert 12 in filled_tree, "Key 12 should be in the filled tree" + assert 18 in filled_tree, "Key 18 should be in the filled tree" + assert 100 not in filled_tree, "Key 100 should not be in the filled tree" \ No newline at end of file From 2ac13c15797966ddf54c996ff4b0b0dc30667b51 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 17:17:02 +0300 Subject: [PATCH 16/22] feat(test): test for operator in added --- tests/test_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index de9bee83..dcfba447 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -96,4 +96,4 @@ def test_contains_operator(filled_tree, empty_tree): assert 7 in filled_tree, "Key 7 should be in the filled tree" assert 12 in filled_tree, "Key 12 should be in the filled tree" assert 18 in filled_tree, "Key 18 should be in the filled tree" - assert 100 not in filled_tree, "Key 100 should not be in the filled tree" \ No newline at end of file + assert 100 not in filled_tree, "Key 100 should not be in the filled tree" From 84e7ee4e8b7eaa045d7168719ff40d76d6e047a8 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 17:25:18 +0300 Subject: [PATCH 17/22] feat(test): test moduling by functionality --- tests/conftest.py | 21 ++++++++ tests/test_basic.py | 99 -------------------------------------- tests/test_contains_len.py | 19 ++++++++ tests/test_delete.py | 9 ++++ tests/test_insert_get.py | 14 ++++++ tests/test_properties.py | 17 +++++++ tests/test_traversal.py | 24 +++++++++ 7 files changed, 104 insertions(+), 99 deletions(-) create mode 100644 tests/conftest.py delete mode 100644 tests/test_basic.py create mode 100644 tests/test_contains_len.py create mode 100644 tests/test_delete.py create mode 100644 tests/test_insert_get.py create mode 100644 tests/test_properties.py create mode 100644 tests/test_traversal.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..eba99035 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,21 @@ +import pytest + +from project.treap import Treap, TreapNode + + +@pytest.fixture +def empty_tree(): + return Treap() + + +@pytest.fixture +def filled_tree(): + tree = Treap() + # Добавляем узлы, чтобы значения value у родителя всегда были больше значений детей + tree.insert_node(10, 100) + tree.insert_node(5, 80) + tree.insert_node(15, 60) + tree.insert_node(7, 90) + tree.insert_node(12, 70) + tree.insert_node(18, 50) + return tree diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index dcfba447..00000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,99 +0,0 @@ -import pytest - -from project.treap import Treap, TreapNode - - -@pytest.fixture -def empty_tree(): - return Treap() - - -@pytest.fixture -def filled_tree(): - tree = Treap() - # Добавляем узлы, чтобы значения value у родителя всегда были больше значений детей - tree.insert_node(10, 100) - tree.insert_node(5, 80) - tree.insert_node(15, 60) - tree.insert_node(7, 90) - tree.insert_node(12, 70) - tree.insert_node(18, 50) - return tree - - -def test_inorder_key_bst(filled_tree): - expected_list = [5, 7, 10, 12, 15, 18] - assert expected_list == list(filled_tree.inorder(filled_tree.root)) - - -def test_insert_and_get_item(empty_tree): - # Проверка вставки и получения значения - empty_tree[10] = 100 - empty_tree[5] = 80 - assert empty_tree[10] == 100, "Value for key 10 should be 100" - assert empty_tree[5] == 80, "Value for key 5 should be 80" - - -def test_get_nonexistent_key(empty_tree): - with pytest.raises(KeyError): - _ = empty_tree[100] - - -def test_delete_item(filled_tree): - # Проверка удаления узла - filled_tree.__delitem__(5) - with pytest.raises(KeyError): - _ = filled_tree[5] - assert filled_tree[10] == 100, "Root node with key 10 should still exist" - - -def test_inorder_traversal(filled_tree): - # Проверка обхода inorder - expected_inorder = [5, 7, 10, 12, 15, 18] - assert ( - list(filled_tree.inorder(filled_tree.root)) == expected_inorder - ), "Inorder traversal does not match expected output" - - -def test_preorder_traversal(filled_tree): - expected_preorder = [10, 7, 5, 12, 15, 18] - assert list(filled_tree.preorder(filled_tree.root)) == expected_preorder - - -def test_postorder_traversal(filled_tree): - expected_postorder = [5, 7, 18, 15, 12, 10] - assert list(filled_tree.postorder(filled_tree.root)) == expected_postorder - - -def test_len_function(filled_tree, empty_tree): - # Проверка количества узлов в дереве - assert len(filled_tree) == 6, "Tree should have 6 nodes" - assert len(empty_tree) == 0, "Empty tree should have 0 nodes" - - -def test_heap_property(filled_tree): - # Проверка свойства кучи (value родителя > value детей) для Treap - def check_heap_property(node): - if node is None: - return True - if node.left and node.left.value > node.value: - return False - if node.right and node.right.value > node.value: - return False - return check_heap_property(node.left) and check_heap_property(node.right) - - assert check_heap_property( - filled_tree.root - ), "Heap property (parent value > children values) should hold for all nodes" - - -def test_contains_operator(filled_tree, empty_tree): - assert 10 not in empty_tree, "Empty tree should not contain any key" - - assert 10 in filled_tree, "Key 10 should be in the filled tree" - assert 5 in filled_tree, "Key 5 should be in the filled tree" - assert 15 in filled_tree, "Key 15 should be in the filled tree" - assert 7 in filled_tree, "Key 7 should be in the filled tree" - assert 12 in filled_tree, "Key 12 should be in the filled tree" - assert 18 in filled_tree, "Key 18 should be in the filled tree" - assert 100 not in filled_tree, "Key 100 should not be in the filled tree" diff --git a/tests/test_contains_len.py b/tests/test_contains_len.py new file mode 100644 index 00000000..92778d68 --- /dev/null +++ b/tests/test_contains_len.py @@ -0,0 +1,19 @@ +import pytest + + +def test_contains_operator(filled_tree, empty_tree): + assert 10 not in empty_tree, "Empty tree should not contain any key" + + assert 10 in filled_tree, "Key 10 should be in the filled tree" + assert 5 in filled_tree, "Key 5 should be in the filled tree" + assert 15 in filled_tree, "Key 15 should be in the filled tree" + assert 7 in filled_tree, "Key 7 should be in the filled tree" + assert 12 in filled_tree, "Key 12 should be in the filled tree" + assert 18 in filled_tree, "Key 18 should be in the filled tree" + assert 100 not in filled_tree, "Key 100 should not be in the filled tree" + + +def test_len_function(filled_tree, empty_tree): + # Проверка количества узлов в дереве + assert len(filled_tree) == 6, "Tree should have 6 nodes" + assert len(empty_tree) == 0, "Empty tree should have 0 nodes" diff --git a/tests/test_delete.py b/tests/test_delete.py new file mode 100644 index 00000000..290e097c --- /dev/null +++ b/tests/test_delete.py @@ -0,0 +1,9 @@ +import pytest + + +def test_delete_item(filled_tree): + # Проверка удаления узла + filled_tree.__delitem__(5) + with pytest.raises(KeyError): + _ = filled_tree[5] + assert filled_tree[10] == 100, "Root node with key 10 should still exist" diff --git a/tests/test_insert_get.py b/tests/test_insert_get.py new file mode 100644 index 00000000..33af29c7 --- /dev/null +++ b/tests/test_insert_get.py @@ -0,0 +1,14 @@ +import pytest + + +def test_insert_and_get_item(empty_tree): + # Проверка вставки и получения значения + empty_tree[10] = 100 + empty_tree[5] = 80 + assert empty_tree[10] == 100, "Value for key 10 should be 100" + assert empty_tree[5] == 80, "Value for key 5 should be 80" + + +def test_get_nonexistent_key(empty_tree): + with pytest.raises(KeyError): + _ = empty_tree[100] diff --git a/tests/test_properties.py b/tests/test_properties.py new file mode 100644 index 00000000..5cdb3469 --- /dev/null +++ b/tests/test_properties.py @@ -0,0 +1,17 @@ +import pytest + + +def test_heap_property(filled_tree): + # Проверка свойства кучи (value родителя > value детей) для Treap + def check_heap_property(node): + if node is None: + return True + if node.left and node.left.value > node.value: + return False + if node.right and node.right.value > node.value: + return False + return check_heap_property(node.left) and check_heap_property(node.right) + + assert check_heap_property( + filled_tree.root + ), "Heap property (parent value > children values) should hold for all nodes" diff --git a/tests/test_traversal.py b/tests/test_traversal.py new file mode 100644 index 00000000..a6337d04 --- /dev/null +++ b/tests/test_traversal.py @@ -0,0 +1,24 @@ +import pytest + + +def test_inorder_key_bst(filled_tree): + expected_list = [5, 7, 10, 12, 15, 18] + assert expected_list == list(filled_tree.inorder(filled_tree.root)) + + +def test_inorder_traversal(filled_tree): + # Проверка обхода inorder + expected_inorder = [5, 7, 10, 12, 15, 18] + assert ( + list(filled_tree.inorder(filled_tree.root)) == expected_inorder + ), "Inorder traversal does not match expected output" + + +def test_preorder_traversal(filled_tree): + expected_preorder = [10, 7, 5, 12, 15, 18] + assert list(filled_tree.preorder(filled_tree.root)) == expected_preorder + + +def test_postorder_traversal(filled_tree): + expected_postorder = [5, 7, 18, 15, 12, 10] + assert list(filled_tree.postorder(filled_tree.root)) == expected_postorder From acc38aa6c3840e5918a706596bb6c16c80a1fbdf Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 17:26:49 +0300 Subject: [PATCH 18/22] feat(test): test moduling by functionality --- tests/test_traversal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_traversal.py b/tests/test_traversal.py index a6337d04..0038b735 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -10,7 +10,7 @@ def test_inorder_traversal(filled_tree): # Проверка обхода inorder expected_inorder = [5, 7, 10, 12, 15, 18] assert ( - list(filled_tree.inorder(filled_tree.root)) == expected_inorder + list(filled_tree.inorder(filled_tree.root)) == expected_inorder ), "Inorder traversal does not match expected output" From 8d09c8b0f20ef084f03eaf383ae6ce87278b3e16 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 17:36:36 +0300 Subject: [PATCH 19/22] feat(test): test moduling by functionality --- tests/{test_contains_len.py => test_treap_contains_len.py} | 0 tests/{test_delete.py => test_treap_delete.py} | 0 tests/{test_insert_get.py => test_treap_insert_get.py} | 0 tests/{test_properties.py => test_treap_properties.py} | 0 tests/{test_traversal.py => test_treap_traversal.py} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename tests/{test_contains_len.py => test_treap_contains_len.py} (100%) rename tests/{test_delete.py => test_treap_delete.py} (100%) rename tests/{test_insert_get.py => test_treap_insert_get.py} (100%) rename tests/{test_properties.py => test_treap_properties.py} (100%) rename tests/{test_traversal.py => test_treap_traversal.py} (100%) diff --git a/tests/test_contains_len.py b/tests/test_treap_contains_len.py similarity index 100% rename from tests/test_contains_len.py rename to tests/test_treap_contains_len.py diff --git a/tests/test_delete.py b/tests/test_treap_delete.py similarity index 100% rename from tests/test_delete.py rename to tests/test_treap_delete.py diff --git a/tests/test_insert_get.py b/tests/test_treap_insert_get.py similarity index 100% rename from tests/test_insert_get.py rename to tests/test_treap_insert_get.py diff --git a/tests/test_properties.py b/tests/test_treap_properties.py similarity index 100% rename from tests/test_properties.py rename to tests/test_treap_properties.py diff --git a/tests/test_traversal.py b/tests/test_treap_traversal.py similarity index 100% rename from tests/test_traversal.py rename to tests/test_treap_traversal.py From c302083708383721868acb764d639aaef8e1faf6 Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 18:02:47 +0300 Subject: [PATCH 20/22] doc(pool): doc added --- project/treap.py | 156 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/project/treap.py b/project/treap.py index 75ed83eb..89cdd18f 100644 --- a/project/treap.py +++ b/project/treap.py @@ -3,12 +3,30 @@ class TreapNode: + """A node in the Treap data structure. + + A TreapNode represents a single node in the Treap, which combines properties + of a binary search tree (BST) and a heap. Each node stores a key, a value, and + references to its left and right children. + + Attributes: + key: The key of the node, used for BST ordering. + value: The value associated with the key, used for heap ordering. + left (Optional[TreapNode]): The left child of the node, or None. + right (Optional[TreapNode]): The right child of the node, or None. + """ key = None value = None left = None right = None def __init__(self, key, value): + """Initialize a new TreapNode with a key and value. + + Args: + key: The key of the node, used for BST ordering. + value: The value of the node, used for heap ordering. + """ self.key = key self.value = value self.left = None @@ -16,51 +34,140 @@ def __init__(self, key, value): class Treap(MutableMapping): + """A Treap data structure that combines a binary search tree and a heap. + + The Treap (Tree + Heap) is a randomized self-balancing binary search tree where + each node has a key and a value. The keys maintain BST properties (left subtree + keys are less than the node's key, right subtree keys are greater), and the values + maintain heap properties (a parent's value is greater than its children's values). + This implementation inherits from MutableMapping, providing dictionary-like + functionality. + + Attributes: + root (Optional[TreapNode]): The root node of the Treap, or None if the Treap + is empty. + """ def __init__(self): + """Initialize an empty Treap.""" self.root = None def __getitem__(self, key): + """Retrieve the value associated with the given key. + + Args: + key: The key to look up in the Treap. + + Returns: + The value associated with the key. + + Raises: + KeyError: If the key is not found in the Treap. + """ node = self.find(self.root, key) if node is not None: return node.value raise KeyError(f"Key {key} not found in Treap") def __setitem__(self, key, value): + """Insert or update a key-value pair in the Treap. + + Args: + key: The key to insert or update. + value: The value to associate with the key. + """ self.insert_node(key, value) def __delitem__(self, key): + """Remove a key-value pair from the Treap. + + Args: + key: The key to remove. + + Raises: + KeyError: If the key is not found in the Treap. + """ self.root = self.remove(self.root, key) def __iter__(self): + """Return an iterator over the keys in the Treap. + + The keys are returned in sorted order (inorder traversal). + + Returns: + An iterator yielding the keys in the Treap. + """ return self.inorder(self.root) def __len__(self): + """Return the number of nodes in the Treap. + + Returns: + int: The number of key-value pairs in the Treap. + """ return self.count_nodes(self.root) def inorder(self, node): + """Perform an inorder traversal of the Treap starting from the given node. + + Args: + node: The root node of the subtree to traverse, or None. + + Yields: + The keys of the nodes in inorder (sorted) order. + """ if node is not None: yield from self.inorder(node.left) yield node.key yield from self.inorder(node.right) def count_nodes(self, node): + """Count the number of nodes in the subtree rooted at the given node. + + Args: + node: The root node of the subtree to count, or None. + + Returns: + int: The number of nodes in the subtree. + """ if node is None: return 0 return 1 + self.count_nodes(node.left) + self.count_nodes(node.right) def preorder(self, node: Optional[TreapNode]): + """Perform a preorder traversal of the Treap starting from the given node. + + Args: + node: The root node of the subtree to traverse, or None. + + Yields: + The keys of the nodes in preorder (root, left, right) order. + """ if node is not None: yield node.key yield from self.preorder(node.left) yield from self.preorder(node.right) def postorder(self, node: Optional[TreapNode]): + """Perform a postorder traversal of the Treap starting from the given node. + + Args: + node: The root node of the subtree to traverse, or None. + + Yields: + The keys of the nodes in postorder (left, right, root) order. + """ if node is not None: yield from self.postorder(node.left) yield from self.postorder(node.right) yield node.key def insert_node(self, key, value): + """Insert a new key-value pair into the Treap. + + Args: + key: The key to insert. + value: The value to associate with the key. + """ new_node = TreapNode(key, value) if self.root is None: self.root = new_node @@ -71,6 +178,19 @@ def insert_node(self, key, value): self.root = self.merge(self.root, right) def remove(self, root: Optional[TreapNode], key): + """Remove a node with the given key from the subtree. + + Args: + root: The root node of the subtree to remove from, or None. + key: The key to remove. + + Returns: + Optional[TreapNode]: The new root of the subtree after removal, or None + if the subtree is empty. + + Raises: + KeyError: If the key is not found in the Treap. + """ if root is None: return None elif root.key < key: @@ -82,6 +202,18 @@ def remove(self, root: Optional[TreapNode], key): return root def split(self, root: Optional[TreapNode], key): + """Split the Treap into two subtrees based on the given key. + + Args: + root: The root node of the subtree to split, or None. + key: The key to split on. + + Returns: + Tuple[Optional[TreapNode], Optional[TreapNode]]: A tuple of two subtrees: + - The left subtree contains all nodes with keys less than the given key. + - The right subtree contains all nodes with keys greater than or equal + to the given key. + """ if root is None: return None, None elif root.key < key: @@ -94,6 +226,20 @@ def split(self, root: Optional[TreapNode], key): return left, root def merge(self, left: Optional[TreapNode], right: Optional[TreapNode]): + """Merge two Treaps into one while maintaining BST and heap properties. + + Args: + left: The root of the left Treap, or None. + right: The root of the right Treap, or None. + + Returns: + Optional[TreapNode]: The root of the merged Treap, or None if both inputs + are None. + + Notes: + Assumes that all keys in the left Treap are less than all keys in the right + Treap. The heap property is maintained by comparing the values of the nodes. + """ if left is None: return right elif right is None: @@ -113,6 +259,16 @@ def merge(self, left: Optional[TreapNode], right: Optional[TreapNode]): return right def find(self, root: Optional[TreapNode], key): + """Find a node with the given key in the subtree. + + Args: + root: The root node of the subtree to search in, or None. + key: The key to search for. + + Returns: + Optional[TreapNode]: The node with the given key, or None if the key is not + found. + """ if root is None: return None elif root.key < key: From 2133e2b82f85715242aaeaa46497c17c5b2040cd Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 18:05:07 +0300 Subject: [PATCH 21/22] doc(pool): doc added --- project/treap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/treap.py b/project/treap.py index 89cdd18f..4c98943b 100644 --- a/project/treap.py +++ b/project/treap.py @@ -15,11 +15,13 @@ class TreapNode: left (Optional[TreapNode]): The left child of the node, or None. right (Optional[TreapNode]): The right child of the node, or None. """ + key = None value = None left = None right = None + def __init__(self, key, value): """Initialize a new TreapNode with a key and value. From 71a04b9c992b1e564229e85c7954c032c95d582b Mon Sep 17 00:00:00 2001 From: Sem4kok Date: Sat, 5 Apr 2025 18:07:00 +0300 Subject: [PATCH 22/22] chore(treap): code beautify --- project/treap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/treap.py b/project/treap.py index 4c98943b..35264f00 100644 --- a/project/treap.py +++ b/project/treap.py @@ -21,7 +21,6 @@ class TreapNode: left = None right = None - def __init__(self, key, value): """Initialize a new TreapNode with a key and value. @@ -49,6 +48,7 @@ class Treap(MutableMapping): root (Optional[TreapNode]): The root node of the Treap, or None if the Treap is empty. """ + def __init__(self): """Initialize an empty Treap.""" self.root = None