Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Run Tests

on:
[ push, pull_request ]

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: Install mypy
run: |
python -m pip install mypy

- name: Run mypy
run: |
mypy .

- name: Run tests
run: |
python ./scripts/run_tests.py
2 changes: 2 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[mypy]
exclude = venv/
280 changes: 280 additions & 0 deletions project/treap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
from collections.abc import MutableMapping
from typing import Optional


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
self.right = None


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
return
else:
left, right = self.split(self.root, key)
self.root = self.merge(left, new_node)
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:
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: 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:
(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: 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:
return left
else:
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):
"""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:
return self.find(root.right, key)
elif root.key > key:
return self.find(root.left, key)
return root
21 changes: 21 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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
18 changes: 0 additions & 18 deletions tests/test_basic.py

This file was deleted.

19 changes: 19 additions & 0 deletions tests/test_treap_contains_len.py
Original file line number Diff line number Diff line change
@@ -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"
9 changes: 9 additions & 0 deletions tests/test_treap_delete.py
Original file line number Diff line number Diff line change
@@ -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"
Loading