Skip to content

Commit c542d41

Browse files
committed
Added fusion tree code and tests to bts for python backend
1 parent f34b7d6 commit c542d41

File tree

4 files changed

+205
-3
lines changed

4 files changed

+205
-3
lines changed

docs/source/pydatastructs/trees/binary_trees.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ Binary Trees
1818
.. autoclass:: pydatastructs.RedBlackTree
1919

2020
.. autoclass:: pydatastructs.BinaryTreeTraversal
21+
22+
.. autoclass:: pydatastructs.FusionTree

pydatastructs/trees/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
CartesianTree,
1818
Treap,
1919
SplayTree,
20-
RedBlackTree
20+
RedBlackTree,
21+
FusionTree
2122
)
2223
__all__.extend(binary_trees.__all__)
2324

pydatastructs/trees/binary_trees.py

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import random
2+
import math
23
from collections import deque as Queue
34
from pydatastructs.utils import TreeNode, CartesianTreeNode, RedBlackTreeNode
45
from pydatastructs.miscellaneous_data_structures import Stack
@@ -17,7 +18,8 @@
1718
'CartesianTree',
1819
'Treap',
1920
'SplayTree',
20-
'RedBlackTree'
21+
'RedBlackTree',
22+
'FusionTree',
2123
]
2224

2325
class BinaryTree(object):
@@ -1887,3 +1889,154 @@ def get_sum(self, left_index, right_index):
18871889
self.get_prefix_sum(left_index - 1)
18881890
else:
18891891
return self.get_prefix_sum(right_index)
1892+
1893+
class FusionTree(object):
1894+
"""
1895+
Implements a Fusion Tree, a multi-way search tree optimized for integer keys.
1896+
1897+
Parameters
1898+
==========
1899+
1900+
key: int
1901+
The integer key to insert.
1902+
root_data: Any
1903+
Optional data to store with the key.
1904+
backend: pydatastructs.Backend
1905+
The backend to be used. Available backends: Python and C++
1906+
Optional, by default, the Python backend is used. For faster execution, use the C++ backend.
1907+
word_size: int
1908+
The size of the integer keys in bits.
1909+
Optional, by default, set to 64.
1910+
1911+
Examples
1912+
========
1913+
1914+
>>> from pydatastructs import FusionTree
1915+
>>> ft = FusionTree()
1916+
>>> ft.insert(1, 1)
1917+
>>> ft.insert(2, 2)
1918+
>>> ft.search(1)
1919+
0
1920+
>>> ft.delete(1)
1921+
True
1922+
>>> ft.search(1)
1923+
None
1924+
1925+
References:
1926+
- https://en.wikipedia.org/wiki/Fusion_tree
1927+
- Fredman & Willard (1990): "Fusion Trees"
1928+
"""
1929+
1930+
__slots__ = ['root_idx', 'tree', 'size', 'B',
1931+
'sketch_mask', 'fingerprint_multiplier']
1932+
1933+
def __new__(cls, key=None, root_data=None, **kwargs):
1934+
backend = kwargs.get('backend', Backend.PYTHON)
1935+
raise_if_backend_is_not_python(cls, backend)
1936+
1937+
obj = object.__new__(cls)
1938+
key = None if root_data is None else key
1939+
root = TreeNode(key, root_data)
1940+
root.is_root = True
1941+
obj.root_idx = 0
1942+
obj.tree, obj.size = ArrayForTrees(TreeNode, [root]), 1
1943+
obj.B = int(math.log2(kwargs.get('word_size', 64))
1944+
** (1/5)) # Multi-way branching factor
1945+
obj.sketch_mask = 0 # Computed dynamically
1946+
obj.fingerprint_multiplier = 2654435761 # Prime multiplier for fingerprinting
1947+
return obj
1948+
1949+
def _compute_sketch_mask(self):
1950+
"""
1951+
Computes a sketch mask for efficient parallel comparisons.
1952+
"""
1953+
keys = [node.key for node in self.tree if node is not None]
1954+
if len(keys) > 1:
1955+
significant_bits = [max(k.bit_length() for k in keys)]
1956+
self.sketch_mask = sum(1 << b for b in significant_bits)
1957+
1958+
def insert(self, key, data=None):
1959+
"""
1960+
Inserts a key into the Fusion Tree.
1961+
1962+
Parameters
1963+
==========
1964+
1965+
key: int
1966+
The integer key to insert.
1967+
data: Any
1968+
Optional data to store with the key.
1969+
"""
1970+
node = TreeNode(key, data)
1971+
self.tree.append(node)
1972+
self.size += 1
1973+
if self.size > 1:
1974+
self._compute_sketch_mask()
1975+
1976+
def _sketch_key(self, key):
1977+
"""
1978+
Applies the sketch mask to compress the key for fast comparison.
1979+
"""
1980+
return key & self.sketch_mask
1981+
1982+
def _fingerprint(self, key):
1983+
"""
1984+
Uses multiplication-based fingerprinting to create a unique identifier
1985+
for the key, allowing fast parallel searches.
1986+
"""
1987+
return (key * self.fingerprint_multiplier) & ((1 << 64) - 1)
1988+
1989+
def search(self, key):
1990+
"""
1991+
Searches for a key in the Fusion Tree using bitwise sketching and fingerprinting.
1992+
1993+
Parameters
1994+
==========
1995+
1996+
key: int
1997+
The integer key to search.
1998+
1999+
Returns
2000+
=======
2001+
2002+
int: The index of the key in the tree, or None if not found.
2003+
"""
2004+
sketch = self._sketch_key(key)
2005+
fingerprint = self._fingerprint(key)
2006+
for i in range(self.size):
2007+
if self._sketch_key(self.tree[i].key) == sketch and self._fingerprint(self.tree[i].key) == fingerprint:
2008+
return i
2009+
return None
2010+
2011+
def delete(self, key):
2012+
"""
2013+
Deletes a key from the Fusion Tree.
2014+
2015+
Parameters
2016+
==========
2017+
2018+
key: int
2019+
The integer key to delete.
2020+
2021+
Returns
2022+
=======
2023+
2024+
bool: True if the key was successfully deleted, False otherwise.
2025+
2026+
"""
2027+
index = self.search(key)
2028+
if index is not None:
2029+
self.tree[index] = None # Soft delete
2030+
# Compact tree
2031+
self.tree = [node for node in self.tree if node is not None]
2032+
self.size -= 1
2033+
if self.size > 1:
2034+
self._compute_sketch_mask()
2035+
return True
2036+
return False
2037+
2038+
def __str__(self):
2039+
"""
2040+
Returns a string representation of the Fusion Tree.
2041+
"""
2042+
return str([(node.key, node.data) for node in self.tree if node is not None])

pydatastructs/trees/tests/test_binary_trees.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pydatastructs.trees.binary_trees import (
2-
BinaryTree, BinarySearchTree, BinaryTreeTraversal, AVLTree,
2+
BinaryTree, BinarySearchTree, BinaryTreeTraversal, AVLTree, FusionTree,
33
ArrayForTrees, BinaryIndexedTree, SelfBalancingBinaryTree, SplayTree, CartesianTree, Treap, RedBlackTree)
44
from pydatastructs.utils.raises_util import raises
55
from pydatastructs.utils.misc_util import TreeNode
@@ -818,3 +818,49 @@ def test_RedBlackTree():
818818

819819
def test_cpp_RedBlackTree():
820820
_test_RedBlackTree(Backend.CPP)
821+
822+
823+
def _test_FusionTree(backend):
824+
FT = FusionTree
825+
f_tree = FT(8, 8, backend=backend)
826+
827+
f_tree.insert(8, 8)
828+
f_tree.insert(3, 3)
829+
f_tree.insert(10, 10)
830+
f_tree.insert(1, 1)
831+
f_tree.insert(6, 6)
832+
f_tree.insert(4, 4)
833+
f_tree.insert(7, 7)
834+
f_tree.insert(14, 14)
835+
f_tree.insert(13, 13)
836+
837+
assert f_tree.search(10) is not None
838+
assert f_tree.search(-1) is None
839+
840+
assert f_tree.delete(13) is True
841+
assert f_tree.search(13) is None
842+
assert f_tree.delete(10) is True
843+
assert f_tree.search(10) is None
844+
assert f_tree.delete(3) is True
845+
assert f_tree.search(3) is None
846+
assert f_tree.delete(13) is False # Already deleted
847+
848+
expected_str = '[(8, 8), (8, 8), (1, 1), (6, 6), (4, 4), (7, 7), (14, 14)]'
849+
assert str(f_tree) == expected_str
850+
851+
f_tree.insert(8, 9)
852+
assert f_tree.search(8) is not None
853+
854+
large_key = 10**9
855+
f_tree.insert(large_key, large_key)
856+
assert f_tree.search(large_key) is not None
857+
858+
expected_str = '[(8, 8), (8, 8), (1, 1), (6, 6), (4, 4), (7, 7), (14, 14), (8, 9), (1000000000, 1000000000)]'
859+
assert str(f_tree) == expected_str
860+
assert f_tree.delete(8) is True
861+
862+
expected_str = '[(8, 8), (1, 1), (6, 6), (4, 4), (7, 7), (14, 14), (8, 9), (1000000000, 1000000000)]'
863+
assert str(f_tree) == expected_str
864+
865+
def test_FusionTree():
866+
_test_FusionTree(Backend.PYTHON)

0 commit comments

Comments
 (0)