From d8d25e7e652ec0c392f119d5e7f9eff6c40200db Mon Sep 17 00:00:00 2001 From: 0xf000000 Date: Tue, 6 Oct 2026 18:29:16 +0200 Subject: [PATCH 1/5] feat: basic structure of bplustree stands split for leafnodes and put are implemented --- src/seriousdb/b_plus_tree.py | 182 +++++++++++++++++++++++++++++++++++ tests/test_b_plus_tree.py | 0 2 files changed, 182 insertions(+) create mode 100644 src/seriousdb/b_plus_tree.py create mode 100644 tests/test_b_plus_tree.py diff --git a/src/seriousdb/b_plus_tree.py b/src/seriousdb/b_plus_tree.py new file mode 100644 index 0000000..cb061ff --- /dev/null +++ b/src/seriousdb/b_plus_tree.py @@ -0,0 +1,182 @@ +"""b+tree implementation for datalookups in O(log n). + +this module will hold the b+tree datastructure for loading and deleting data in and out of memory +NO function is currently thread-safe in this class. +""" + +from __future__ import annotations + + +class _Node: + """internal class representing a node from the b+Tree.""" + + _parent: _InternalNode | None = None + _keys: list[int] + + def __init__(self) -> None: + self._keys = [] + + +class _LeafNode(_Node): + """internal class representing a _leafnode from the b+Tree.""" + + _values: list + _next_leaf: _LeafNode | None = None + + def __init__(self) -> None: + self._values = [] + + +class _InternalNode(_Node): + """internal class representing.""" + + _children: list[_Node] + + def __init__(self) -> None: + self._children = [] + + +class bPlusTree: + """a Btree like Datastructure to retrieve and put data in O(log n) time. + + the public functions will firstly be get() put() delete() range() + + !IMPORTANT: this structure is currently not threadsafe. + + Parameters + ---------- + _maxChildren :int + the tree takes in the maximum number of children a internal node should have. + + Attributes + ---------- + _maxChildren : int + The maximum number of children (mostly in literature visualized as m) an internal Node can point to + _maxKeys: int + The maximum number of Keys a _Node can hold derrived from the maximum number of children (m -1) + _root : _Node or None + the root Node of the tree the first will be a leafNode and after enough insertions mostly an internal Node + """ + + _maxChildren: int = 0 + _maxKeys: int = 0 + _root: _LeafNode | _InternalNode | None = None + + def __init__(self, maxChildren: int) -> None: + self._maxChildren = maxChildren + self._maxKeys = self._maxChildren - 1 + + def put(self, key: int, value) -> bool: + """Put an key value pair into the tree. + + if the tree is empty a leafNode is created at root and filled with the first key and value. + if the tree alereay has some nodes the search starts. The next node from the children gets chosen via the comparator keys. + If a leafnode has to many values or keys put splits the leafnote. + + Parameters + ---------- + key : int + the key to access the value. + value : any + Value to store. + + Returns + ------- + is_new_key : bool + ``True`` if `key` did not exist before, ``False`` if an existing + value was replaced. + """ + if self._root is None: + self._root = self._createLeaf(key, value) + return True + + node: _Node | _LeafNode | _InternalNode = self._root + while True: + if isinstance(node, _LeafNode): + if key in node._keys: + key_index = node._keys.index(key) + node._values.insert(key_index, value) + return False + + node._keys.append(key) + node._values.append(value) + if len(node._keys) > self._maxKeys: + self._split(node) + return True + + found: bool = False + if isinstance(node, _InternalNode): + internalNode: _InternalNode = node + for i in range(0, len(internalNode._keys), 1): + if key < internalNode._keys[i]: + node = internalNode._children[i] + found = True + break + + if found is False: + node = internalNode._children[-1] + + def get(key: int) -> bytes | None: + """Return a value from the given key. if the key does not exist in the tree this function returns None. + + Parameters + ---------- + key : int + the key to access the value. + + Returns + ------- + value : bytes | None + returns bytes if the key is in the tree otherwise this function returns None + """ + return + + def _createLeaf(self, key: int, value) -> _LeafNode: + node = _LeafNode() + node._values.append(value) + node._keys.append(value) + return node + + def _split(self, node: _Node) -> None: + + if isinstance(node, _LeafNode): + newLeaf: _LeafNode = _LeafNode() + keyLen: int = len(node._keys) + splitIndex: int = keyLen // 2 + + newLeaf._keys = node._keys[splitIndex:] + newLeaf._values = node._values[splitIndex:] + + node._keys = node._keys[:splitIndex] + node._values = node._values[:splitIndex] + + newLeaf._next_leaf = node._next_leaf + node._next_leaf = newLeaf + + newSeperator: int = newLeaf._keys[0] + + if node._parent is not None: + parent: _InternalNode = node._parent + + child_index: int = parent._children.index(node) + parent._children.insert(child_index + 1, newLeaf) + parent._keys.insert(child_index, newSeperator) + newLeaf._parent = parent + + if len(parent._keys) > self._maxKeys: + self._split(parent) + + else: + internalNode: _InternalNode = _InternalNode() + internalNode._keys.append(newSeperator) + internalNode._children.append(node) + internalNode._children.append(newLeaf) + + newLeaf._parent = internalNode + node._parent = internalNode + self._root = internalNode + + if isinstance(node, _InternalNode): + return + + return diff --git a/tests/test_b_plus_tree.py b/tests/test_b_plus_tree.py new file mode 100644 index 0000000..e69de29 From a8749fb605184a6dad850c62c124baa1ba9fb08d Mon Sep 17 00:00:00 2001 From: 0xf000000 Date: Tue, 6 Oct 2026 21:12:14 +0200 Subject: [PATCH 2/5] feat: first split function is implemented but needs to get refactored --- src/seriousdb/b_plus_tree.py | 136 ++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 40 deletions(-) diff --git a/src/seriousdb/b_plus_tree.py b/src/seriousdb/b_plus_tree.py index cb061ff..ddd9cf5 100644 --- a/src/seriousdb/b_plus_tree.py +++ b/src/seriousdb/b_plus_tree.py @@ -24,6 +24,7 @@ class _LeafNode(_Node): _next_leaf: _LeafNode | None = None def __init__(self) -> None: + super().__init__() self._values = [] @@ -33,6 +34,7 @@ class _InternalNode(_Node): _children: list[_Node] def __init__(self) -> None: + super().__init__() self._children = [] @@ -66,7 +68,7 @@ def __init__(self, maxChildren: int) -> None: self._maxChildren = maxChildren self._maxKeys = self._maxChildren - 1 - def put(self, key: int, value) -> bool: + def put(self, key: int, value: bytes) -> bool: """Put an key value pair into the tree. if the tree is empty a leafNode is created at root and filled with the first key and value. @@ -77,8 +79,8 @@ def put(self, key: int, value) -> bool: ---------- key : int the key to access the value. - value : any - Value to store. + value : bytes + bytes (Pages) to store. Returns ------- @@ -95,26 +97,22 @@ def put(self, key: int, value) -> bool: if isinstance(node, _LeafNode): if key in node._keys: key_index = node._keys.index(key) - node._values.insert(key_index, value) + node._values[key_index] = value return False - node._keys.append(key) - node._values.append(value) + insert_index: int = self._find_position(node._keys, key) + + node._keys.insert(insert_index, key) + node._values.insert(insert_index, value) + if len(node._keys) > self._maxKeys: self._split(node) return True - found: bool = False if isinstance(node, _InternalNode): internalNode: _InternalNode = node - for i in range(0, len(internalNode._keys), 1): - if key < internalNode._keys[i]: - node = internalNode._children[i] - found = True - break - - if found is False: - node = internalNode._children[-1] + nodeIndex = self._find_position(internalNode._keys, key) + node = internalNode._children[nodeIndex] def get(key: int) -> bytes | None: """Return a value from the given key. if the key does not exist in the tree this function returns None. @@ -131,42 +129,56 @@ def get(key: int) -> bytes | None: """ return + def delete(key: int) -> bool: + """Delete algorithm deletes key value pair.""" + return True + + """------ INTERNAL FUNCTIONS ------""" + def _createLeaf(self, key: int, value) -> _LeafNode: node = _LeafNode() - node._values.append(value) - node._keys.append(value) + node._keys.append(key) + key_index = node._keys.index(key) + node._values.insert(key_index, value) return node - def _split(self, node: _Node) -> None: + def _find_position(self, keys: list[int], key: int) -> int: + insert_index: int = 0 - if isinstance(node, _LeafNode): - newLeaf: _LeafNode = _LeafNode() - keyLen: int = len(node._keys) - splitIndex: int = keyLen // 2 + while insert_index <= len(keys) and keys[insert_index] < key: + insert_index += 1 - newLeaf._keys = node._keys[splitIndex:] - newLeaf._values = node._values[splitIndex:] + return insert_index - node._keys = node._keys[:splitIndex] - node._values = node._values[:splitIndex] + def _split_leaf(self, node: _LeafNode) -> None: + newLeaf: _LeafNode = _LeafNode() + keyLen: int = len(node._keys) + splitIndex: int = keyLen // 2 - newLeaf._next_leaf = node._next_leaf - node._next_leaf = newLeaf + newLeaf._keys = node._keys[splitIndex:] + newLeaf._values = node._values[splitIndex:] - newSeperator: int = newLeaf._keys[0] + node._keys = node._keys[:splitIndex] + node._values = node._values[:splitIndex] - if node._parent is not None: - parent: _InternalNode = node._parent + newLeaf._next_leaf = node._next_leaf + node._next_leaf = newLeaf - child_index: int = parent._children.index(node) - parent._children.insert(child_index + 1, newLeaf) - parent._keys.insert(child_index, newSeperator) - newLeaf._parent = parent + newSeperator: int = newLeaf._keys[0] - if len(parent._keys) > self._maxKeys: - self._split(parent) + if node._parent is not None: + parent: _InternalNode = node._parent + + child_index: int = parent._children.index(node) + parent._children.insert(child_index + 1, newLeaf) + parent._keys.insert(child_index, newSeperator) + newLeaf._parent = parent + + if len(parent._keys) > self._maxKeys: + self._split(parent) else: + # if the parent of an leafnote is None it is guaranteed that the leafnote lies at root. internalNode: _InternalNode = _InternalNode() internalNode._keys.append(newSeperator) internalNode._children.append(node) @@ -176,7 +188,51 @@ def _split(self, node: _Node) -> None: node._parent = internalNode self._root = internalNode - if isinstance(node, _InternalNode): - return + def _split_internal(self, node: _InternalNode) -> None: + oldNode = node + newNode = _InternalNode() - return + keyLen: int = len(node._keys) + splitIndex: int = keyLen // 2 + + tempKey = oldNode._keys[splitIndex] + newNode._keys = oldNode._keys[splitIndex + 1 :] + oldNode._keys = oldNode._keys[:splitIndex] + + newNode._children = oldNode._children[splitIndex + 1 :] + oldNode._children = oldNode._children[: splitIndex + 1] + + # fix child parent pointers + + for child in newNode._children: + child._parent = newNode + + if oldNode._parent is not None: + parent: _InternalNode = oldNode._parent + + child_index: int = parent._children.index(oldNode) + parent._children.insert(child_index + 1, newNode) + parent._keys.insert(child_index, tempKey) + newNode._parent = parent + + if len(parent._keys) > self._maxKeys: + self._split(parent) + else: + internalNode: _InternalNode = _InternalNode() + internalNode._keys.append(tempKey) + + internalNode._children.append(oldNode) + internalNode._children.append(newNode) + + newNode._parent = internalNode + oldNode._parent = internalNode + + self._root = internalNode + + def _split(self, node: _Node) -> None: + + if isinstance(node, _LeafNode): + self._split_leaf(node) + + if isinstance(node, _InternalNode): + self._split_internal(node) From a38adac7404efb782eb2b03ab6a2dadabfb83275 Mon Sep 17 00:00:00 2001 From: 0xf000000 Date: Wed, 7 Oct 2026 11:26:35 +0200 Subject: [PATCH 3/5] feat: wrote basic tests now and refactored tree split functionality a bit --- src/seriousdb/b_plus_tree.py | 96 +++++++++++++++++++----------------- tests/test_b_plus_tree.py | 22 +++++++++ 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/src/seriousdb/b_plus_tree.py b/src/seriousdb/b_plus_tree.py index ddd9cf5..d44e877 100644 --- a/src/seriousdb/b_plus_tree.py +++ b/src/seriousdb/b_plus_tree.py @@ -1,7 +1,7 @@ """b+tree implementation for datalookups in O(log n). this module will hold the b+tree datastructure for loading and deleting data in and out of memory -NO function is currently thread-safe in this class. +!IMPORTANT this implementation is not Threadsafe """ from __future__ import annotations @@ -10,22 +10,24 @@ class _Node: """internal class representing a node from the b+Tree.""" - _parent: _InternalNode | None = None + _parent: _InternalNode | None _keys: list[int] def __init__(self) -> None: + self._parent = None self._keys = [] class _LeafNode(_Node): """internal class representing a _leafnode from the b+Tree.""" - _values: list - _next_leaf: _LeafNode | None = None + _values: list[bytes] + _next_leaf: _LeafNode | None def __init__(self) -> None: super().__init__() self._values = [] + self._next_leaf = None class _InternalNode(_Node): @@ -62,11 +64,12 @@ class bPlusTree: _maxChildren: int = 0 _maxKeys: int = 0 - _root: _LeafNode | _InternalNode | None = None + _root: _LeafNode | _InternalNode | None def __init__(self, maxChildren: int) -> None: self._maxChildren = maxChildren self._maxKeys = self._maxChildren - 1 + self._root = None def put(self, key: int, value: bytes) -> bool: """Put an key value pair into the tree. @@ -88,33 +91,34 @@ def put(self, key: int, value: bytes) -> bool: ``True`` if `key` did not exist before, ``False`` if an existing value was replaced. """ + is_new_key = True + if self._root is None: self._root = self._createLeaf(key, value) - return True + return is_new_key - node: _Node | _LeafNode | _InternalNode = self._root + node: _LeafNode | _InternalNode | _Node = self._root while True: if isinstance(node, _LeafNode): - if key in node._keys: + if self._isKeyInLeaf(node, key): key_index = node._keys.index(key) node._values[key_index] = value return False + # NOTE: this find_position call only works because we check for duplicates in the if case before maby for later its more smart to make 2 helpers out of this insert_index: int = self._find_position(node._keys, key) node._keys.insert(insert_index, key) node._values.insert(insert_index, value) - if len(node._keys) > self._maxKeys: - self._split(node) + self._check_split(node) return True - - if isinstance(node, _InternalNode): + elif isinstance(node, _InternalNode): internalNode: _InternalNode = node nodeIndex = self._find_position(internalNode._keys, key) node = internalNode._children[nodeIndex] - def get(key: int) -> bytes | None: + def get(self, key: int) -> bytes | None: """Return a value from the given key. if the key does not exist in the tree this function returns None. Parameters @@ -129,7 +133,7 @@ def get(key: int) -> bytes | None: """ return - def delete(key: int) -> bool: + def delete(self, key: int) -> bool: """Delete algorithm deletes key value pair.""" return True @@ -142,10 +146,16 @@ def _createLeaf(self, key: int, value) -> _LeafNode: node._values.insert(key_index, value) return node + def _createInternal(self, key: int) -> None: + return + + def _isKeyInLeaf(self, node: _LeafNode, key: int) -> bool: + return key in node._keys + def _find_position(self, keys: list[int], key: int) -> int: insert_index: int = 0 - while insert_index <= len(keys) and keys[insert_index] < key: + while insert_index < len(keys) and keys[insert_index] <= key: insert_index += 1 return insert_index @@ -166,7 +176,17 @@ def _split_leaf(self, node: _LeafNode) -> None: newSeperator: int = newLeaf._keys[0] - if node._parent is not None: + if node._parent is None: + # if the parent of an leafnote is None it is guaranteed that the leafnote lies at root. + internalNode: _InternalNode = _InternalNode() + internalNode._keys.append(newSeperator) + internalNode._children.append(node) + internalNode._children.append(newLeaf) + + newLeaf._parent = internalNode + node._parent = internalNode + self._root = internalNode + else: parent: _InternalNode = node._parent child_index: int = parent._children.index(node) @@ -174,19 +194,7 @@ def _split_leaf(self, node: _LeafNode) -> None: parent._keys.insert(child_index, newSeperator) newLeaf._parent = parent - if len(parent._keys) > self._maxKeys: - self._split(parent) - - else: - # if the parent of an leafnote is None it is guaranteed that the leafnote lies at root. - internalNode: _InternalNode = _InternalNode() - internalNode._keys.append(newSeperator) - internalNode._children.append(node) - internalNode._children.append(newLeaf) - - newLeaf._parent = internalNode - node._parent = internalNode - self._root = internalNode + self._check_split(parent) def _split_internal(self, node: _InternalNode) -> None: oldNode = node @@ -202,22 +210,10 @@ def _split_internal(self, node: _InternalNode) -> None: newNode._children = oldNode._children[splitIndex + 1 :] oldNode._children = oldNode._children[: splitIndex + 1] - # fix child parent pointers - for child in newNode._children: child._parent = newNode - if oldNode._parent is not None: - parent: _InternalNode = oldNode._parent - - child_index: int = parent._children.index(oldNode) - parent._children.insert(child_index + 1, newNode) - parent._keys.insert(child_index, tempKey) - newNode._parent = parent - - if len(parent._keys) > self._maxKeys: - self._split(parent) - else: + if oldNode._parent is None: internalNode: _InternalNode = _InternalNode() internalNode._keys.append(tempKey) @@ -228,11 +224,21 @@ def _split_internal(self, node: _InternalNode) -> None: oldNode._parent = internalNode self._root = internalNode + else: + parent: _InternalNode = oldNode._parent - def _split(self, node: _Node) -> None: + child_index: int = parent._children.index(oldNode) + parent._children.insert(child_index + 1, newNode) + parent._keys.insert(child_index, tempKey) + newNode._parent = parent + + self._check_split(parent) + + def _check_split(self, node: _Node | _InternalNode | _LeafNode) -> None: + if len(node._keys) <= self._maxKeys: + return if isinstance(node, _LeafNode): self._split_leaf(node) - - if isinstance(node, _InternalNode): + elif isinstance(node, _InternalNode): self._split_internal(node) diff --git a/tests/test_b_plus_tree.py b/tests/test_b_plus_tree.py index e69de29..cadc908 100644 --- a/tests/test_b_plus_tree.py +++ b/tests/test_b_plus_tree.py @@ -0,0 +1,22 @@ +"""tests for the b+Tree implementation""" + +from seriousdb.b_plus_tree import bPlusTree + + +def test_put_first_key(): + maxChildren: int = 3 + tree: bPlusTree = bPlusTree(maxChildren) + + result = tree.put(1, b"2") + + assert result is True + + +def test_put_same_key(): + maxChildren: int = 3 + tree: bPlusTree = bPlusTree(maxChildren) + + tree.put(1, b"2") + result: bool = tree.put(1, b"2") + + assert result is False From 9a561d4d107816b40d895e770a2260a9aad0339e Mon Sep 17 00:00:00 2001 From: 0xf000000 Date: Wed, 7 Oct 2026 21:18:44 +0200 Subject: [PATCH 4/5] feat: wrote get implementation and some additional tests --- src/seriousdb/b_plus_tree.py | 19 +++++++-- tests/test_b_plus_tree.py | 74 +++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/seriousdb/b_plus_tree.py b/src/seriousdb/b_plus_tree.py index d44e877..0a54113 100644 --- a/src/seriousdb/b_plus_tree.py +++ b/src/seriousdb/b_plus_tree.py @@ -131,7 +131,21 @@ def get(self, key: int) -> bytes | None: value : bytes | None returns bytes if the key is in the tree otherwise this function returns None """ - return + value: bytes | None = None + if self._root is None: + return value + + node: _InternalNode | _LeafNode | _Node = self._root + while isinstance(node, _InternalNode): + nodeIndex: int = self._find_position(node._keys, key) + node = node._children[nodeIndex] + + # node should always be a _LeafNode this might be to defensive + if isinstance(node, _LeafNode) and key in node._keys: + keyIndex: int = node._keys.index(key) + value = node._values[keyIndex] + + return value def delete(self, key: int) -> bool: """Delete algorithm deletes key value pair.""" @@ -146,9 +160,6 @@ def _createLeaf(self, key: int, value) -> _LeafNode: node._values.insert(key_index, value) return node - def _createInternal(self, key: int) -> None: - return - def _isKeyInLeaf(self, node: _LeafNode, key: int) -> bool: return key in node._keys diff --git a/tests/test_b_plus_tree.py b/tests/test_b_plus_tree.py index cadc908..18430ae 100644 --- a/tests/test_b_plus_tree.py +++ b/tests/test_b_plus_tree.py @@ -1,6 +1,28 @@ """tests for the b+Tree implementation""" -from seriousdb.b_plus_tree import bPlusTree +from seriousdb.b_plus_tree import _InternalNode, _LeafNode, bPlusTree + + +def assertTreeStructure(tree: bPlusTree) -> None: + node = tree._root + # okay here i will just make it a really long for loop so it cannot run infenetly if there is an error in the tree code + children = [] + for i in range(0, 5000000, 1): + if isinstance(node, _LeafNode): + if node is not tree._root: + assert node._parent is not None + assert isinstance(node._parent, _LeafNode) is not True + assert len(node._keys) <= tree._maxKeys + assert len(node._values) == len(node._keys) + return + if isinstance(node, _InternalNode): + if node is not tree._root: + assert node._parent is not None + assert len(node._keys) <= tree._maxKeys + for child in node._children: + if child not in children: + children.append(child) + node = children.pop() def test_put_first_key(): @@ -20,3 +42,53 @@ def test_put_same_key(): result: bool = tree.put(1, b"2") assert result is False + + +def test_force_split(): + maxChildren: int = 3 + tree: bPlusTree = bPlusTree(maxChildren) + tree.put(1, b"AA") + tree.put(2, b"AA") + tree.put(3, b"AA") + tree.put(4, b"AA") + + assertTreeStructure(tree) + assert isinstance(tree._root, _InternalNode) + + +def test_stress_put(): + maxChildren: int = 3 + tree: bPlusTree = bPlusTree(maxChildren) + for i in range(0, 100000, 1): + tree.put(i, b"bbb") + + assertTreeStructure(tree) + + +def test_get_success(): + maxChildren: int = 3 + tree: bPlusTree = bPlusTree(maxChildren) + + tree.put(1, b"AA") + value = tree.get(1) + + assert value == b"AA" + + +def test_get_empyTree(): + maxChildren: int = 3 + tree: bPlusTree = bPlusTree(maxChildren) + # retrive + value = tree.get(1) + assert value == None + + +def test_get_KeyNotInTree(): + maxChildren: int = 3 + tree: bPlusTree = bPlusTree(maxChildren) + + tree.put(2, b"AA") + tree.put(3, b"BB") + + value = tree.get(5) + assert value == None From a5b78556edc2eafd9e1957e1ef5323a1a0fef88a Mon Sep 17 00:00:00 2001 From: 0xf000000 Date: Fri, 9 Oct 2026 22:43:36 +0200 Subject: [PATCH 5/5] fix: fixed bugs in testcode to assert treestructure also wrote some more tests --- src/seriousdb/b_plus_tree.py | 46 +++++++++++++++++++++++--------- tests/test_b_plus_tree.py | 51 ++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/seriousdb/b_plus_tree.py b/src/seriousdb/b_plus_tree.py index 0a54113..3081a01 100644 --- a/src/seriousdb/b_plus_tree.py +++ b/src/seriousdb/b_plus_tree.py @@ -40,7 +40,7 @@ def __init__(self) -> None: self._children = [] -class bPlusTree: +class BPlusTree: """a Btree like Datastructure to retrieve and put data in O(log n) time. the public functions will firstly be get() put() delete() range() @@ -60,16 +60,24 @@ class bPlusTree: The maximum number of Keys a _Node can hold derrived from the maximum number of children (m -1) _root : _Node or None the root Node of the tree the first will be a leafNode and after enough insertions mostly an internal Node + + Raises + ------ + ValueError if the maxchildren parameter is below 3 """ _maxChildren: int = 0 _maxKeys: int = 0 _root: _LeafNode | _InternalNode | None + _size: int = 0 def __init__(self, maxChildren: int) -> None: + if maxChildren < 3: + raise ValueError("maxChildren must be at least 3") self._maxChildren = maxChildren self._maxKeys = self._maxChildren - 1 self._root = None + self._size = 0 def put(self, key: int, value: bytes) -> bool: """Put an key value pair into the tree. @@ -92,7 +100,7 @@ def put(self, key: int, value: bytes) -> bool: value was replaced. """ is_new_key = True - + self._size += 1 if self._root is None: self._root = self._createLeaf(key, value) return is_new_key @@ -147,6 +155,16 @@ def get(self, key: int) -> bytes | None: return value + def size(self) -> int: + """Return the current keyvaluePairs in the tree. + + Returns + ------- + value : int + returns the number of keyvalue pairs in the tree + """ + return self._size + def delete(self, key: int) -> bool: """Delete algorithm deletes key value pair.""" return True @@ -160,6 +178,14 @@ def _createLeaf(self, key: int, value) -> _LeafNode: node._values.insert(key_index, value) return node + def _createInternal(self, key, child1, child2) -> _InternalNode: + internalNode: _InternalNode = _InternalNode() + internalNode._keys.append(key) + internalNode._children.append(child1) + internalNode._children.append(child2) + + return internalNode + def _isKeyInLeaf(self, node: _LeafNode, key: int) -> bool: return key in node._keys @@ -189,10 +215,9 @@ def _split_leaf(self, node: _LeafNode) -> None: if node._parent is None: # if the parent of an leafnote is None it is guaranteed that the leafnote lies at root. - internalNode: _InternalNode = _InternalNode() - internalNode._keys.append(newSeperator) - internalNode._children.append(node) - internalNode._children.append(newLeaf) + internalNode: _InternalNode = self._createInternal( + newSeperator, node, newLeaf + ) newLeaf._parent = internalNode node._parent = internalNode @@ -225,15 +250,12 @@ def _split_internal(self, node: _InternalNode) -> None: child._parent = newNode if oldNode._parent is None: - internalNode: _InternalNode = _InternalNode() - internalNode._keys.append(tempKey) - - internalNode._children.append(oldNode) - internalNode._children.append(newNode) + internalNode: _InternalNode = self._createInternal( + tempKey, oldNode, newNode + ) newNode._parent = internalNode oldNode._parent = internalNode - self._root = internalNode else: parent: _InternalNode = oldNode._parent diff --git a/tests/test_b_plus_tree.py b/tests/test_b_plus_tree.py index 18430ae..38fdae9 100644 --- a/tests/test_b_plus_tree.py +++ b/tests/test_b_plus_tree.py @@ -1,33 +1,34 @@ """tests for the b+Tree implementation""" -from seriousdb.b_plus_tree import _InternalNode, _LeafNode, bPlusTree +from seriousdb.b_plus_tree import BPlusTree, _InternalNode, _LeafNode, _Node -def assertTreeStructure(tree: bPlusTree) -> None: - node = tree._root +def assertTreeStructure(tree: BPlusTree) -> None: + root = tree._root + + if root is None: + return # okay here i will just make it a really long for loop so it cannot run infenetly if there is an error in the tree code - children = [] - for i in range(0, 5000000, 1): + stack = [root] + while len(stack) > 0: + node: _Node = stack.pop() + + assert len(node._keys) <= tree._maxKeys + + if node is not tree._root: + assert node._parent is not None + if isinstance(node, _LeafNode): - if node is not tree._root: - assert node._parent is not None - assert isinstance(node._parent, _LeafNode) is not True - assert len(node._keys) <= tree._maxKeys - assert len(node._values) == len(node._keys) - return + assert len(node._keys) == len(node._values) + if isinstance(node, _InternalNode): - if node is not tree._root: - assert node._parent is not None - assert len(node._keys) <= tree._maxKeys - for child in node._children: - if child not in children: - children.append(child) - node = children.pop() + assert len(node._children) == len(node._keys) + 1 + stack.extend(node._children) def test_put_first_key(): maxChildren: int = 3 - tree: bPlusTree = bPlusTree(maxChildren) + tree: BPlusTree = BPlusTree(maxChildren) result = tree.put(1, b"2") @@ -36,7 +37,7 @@ def test_put_first_key(): def test_put_same_key(): maxChildren: int = 3 - tree: bPlusTree = bPlusTree(maxChildren) + tree: BPlusTree = BPlusTree(maxChildren) tree.put(1, b"2") result: bool = tree.put(1, b"2") @@ -46,7 +47,7 @@ def test_put_same_key(): def test_force_split(): maxChildren: int = 3 - tree: bPlusTree = bPlusTree(maxChildren) + tree: BPlusTree = BPlusTree(maxChildren) tree.put(1, b"AA") tree.put(2, b"AA") tree.put(3, b"AA") @@ -58,7 +59,7 @@ def test_force_split(): def test_stress_put(): maxChildren: int = 3 - tree: bPlusTree = bPlusTree(maxChildren) + tree: BPlusTree = BPlusTree(maxChildren) for i in range(0, 100000, 1): tree.put(i, b"bbb") @@ -67,7 +68,7 @@ def test_stress_put(): def test_get_success(): maxChildren: int = 3 - tree: bPlusTree = bPlusTree(maxChildren) + tree: BPlusTree = BPlusTree(maxChildren) tree.put(1, b"AA") value = tree.get(1) @@ -77,7 +78,7 @@ def test_get_success(): def test_get_empyTree(): maxChildren: int = 3 - tree: bPlusTree = bPlusTree(maxChildren) + tree: BPlusTree = BPlusTree(maxChildren) # retrive value = tree.get(1) assert value == None @@ -85,7 +86,7 @@ def test_get_empyTree(): def test_get_KeyNotInTree(): maxChildren: int = 3 - tree: bPlusTree = bPlusTree(maxChildren) + tree: BPlusTree = BPlusTree(maxChildren) tree.put(2, b"AA") tree.put(3, b"BB")