diff --git a/AUTHORS b/AUTHORS index 478ec0785..b0d77787d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,4 +9,6 @@ Prashant Rawat Harsheet Pratik Goyal Jay Thorat +Abdelraman Khaled Fouad Rajveer Singh Bharadwaj + diff --git a/docs/source/pydatastructs/pydatastructs.rst b/docs/source/pydatastructs/pydatastructs.rst index cb33622c7..941f214be 100644 --- a/docs/source/pydatastructs/pydatastructs.rst +++ b/docs/source/pydatastructs/pydatastructs.rst @@ -10,4 +10,4 @@ Modules graphs/graphs.rst strings/strings.rst trees/trees.rst - miscellaneous_data_structures/miscellaneous_data_structures.rst \ No newline at end of file + miscellaneous_data_structures/miscellaneous_data_structures.rst diff --git a/docs/source/pydatastructs/trees/binary_trees.rst b/docs/source/pydatastructs/trees/binary_trees.rst index f24e21621..a5d63b468 100644 --- a/docs/source/pydatastructs/trees/binary_trees.rst +++ b/docs/source/pydatastructs/trees/binary_trees.rst @@ -9,6 +9,8 @@ Binary Trees .. autoclass:: pydatastructs.BinaryIndexedTree +.. autoclass:: pydatastructs.BinaryIndexedTree2D + .. autoclass:: pydatastructs.CartesianTree .. autoclass:: pydatastructs.Treap @@ -18,3 +20,4 @@ Binary Trees .. autoclass:: pydatastructs.RedBlackTree .. autoclass:: pydatastructs.BinaryTreeTraversal + diff --git a/pydatastructs/trees/__init__.py b/pydatastructs/trees/__init__.py index 6b9df8a22..c8715c1c7 100644 --- a/pydatastructs/trees/__init__.py +++ b/pydatastructs/trees/__init__.py @@ -16,7 +16,9 @@ CartesianTree, Treap, SplayTree, - RedBlackTree + RedBlackTree, + BinaryIndexedTree2D, + BinaryIndexedTreeNd ) __all__.extend(binary_trees.__all__) diff --git a/pydatastructs/trees/binary_trees.py b/pydatastructs/trees/binary_trees.py index 34b265144..f5cf59921 100644 --- a/pydatastructs/trees/binary_trees.py +++ b/pydatastructs/trees/binary_trees.py @@ -3,6 +3,7 @@ from pydatastructs.utils import TreeNode, CartesianTreeNode, RedBlackTreeNode from pydatastructs.miscellaneous_data_structures import Stack from pydatastructs.linear_data_structures import OneDimensionalArray +from pydatastructs.linear_data_structures import MultiDimensionalArray from pydatastructs.linear_data_structures.arrays import ArrayForTrees from pydatastructs.utils.misc_util import ( Backend, raise_if_backend_is_not_python) @@ -16,7 +17,10 @@ 'CartesianTree', 'Treap', 'SplayTree', - 'RedBlackTree' + 'RedBlackTree', + 'BinaryIndexedTree2D', + 'BinaryIndexedTreeNd' + ] class BinaryTree(object): @@ -1728,3 +1732,293 @@ def get_sum(self, left_index, right_index): self.get_prefix_sum(left_index - 1) else: return self.get_prefix_sum(right_index) + +class BinaryIndexedTree2D(object): + """ + 2D Fenwick tree aka Binary indexed tree(BIT) + + Parameters + ======== + + array: 2d list/tuple + The array whose elements are to be + considered for the queries. + backend: pydatastructs.Backend + The backend to be used. + Optional, by default, the best available + backend is used. + Examples + ======== + + >>> from pydatastructs import BinaryIndexedTree2D + >>> bit = BinaryIndexedTree([[1, 2, 3] ,\ + [2 , 3 ,4 ] ,\ + [8 ,6 ,7 ]]) + >>> bit.get_area(0, 0 , 1 ,1 ) + 8 + >>> bit.add(1 ,1 , 3) + >>> bit.get_sum(0, 0 , 1 , 1 ) + 11 + + """ + __slots__ = ['tree', 'array' , 'n' ,'m'] + + def __new__(cls, array, **kwargs): + raise_if_backend_is_not_python(cls, kwargs.get('backend', Backend.PYTHON)) + obj = object.__new__(cls) + obj.n = len(array) + obj.m = len(array[0]) + obj.array = [[0 for i in range(obj.m+1)] for j in range(obj.n+1)] + obj.tree = [[0 for i in range(obj.m+1 )] for j in range(obj.n+1 )] + + + for i in obj.array: + for j in i : + j = 0 + for i in obj.tree: + for j in i: + j = 0 + + for i in range(obj.n): + for j in range(obj.m): + obj.array[i][j] = array[i][j] + + for i in range(obj.n): + for j in range(obj.m): + obj.add(i ,j , obj.array[i][j]) + return obj + + @classmethod + def methods(cls): + return ['add','get_area'] + def add(self ,x , y , val ): + """ + Add `val` to your value in position [x][y] aka yorArr[x][y]+=val + + Parameters + ======== + + x :int + x of of point + y :int + of point + val :int + value to add + + Returns + ========= + + None + + """ + i = x +1 + while i<= self.n: + j = y +1 + while j<= self.m: + self.tree[i-1][j-1] += val + j+= j & (-j) + i += i &(-i) + + def _get_sum_to_origin(self , x , y ): + res =0 + i = x +1 + while i >0: + j = y +1 + while j >0 : + res += self.tree[i - 1][j - 1] + j -= j & (-j) + i -= i & (-i) + return res + + def get_area(self, start_x ,start_y ,end_x , end_y ): + """ + Get area of rectangle that has up left corner of point(start_x , start_y ) and down right coener of (end_x , end_y).\n + let S = start point and E = end point + Saaaaa\n + aaaaaa\n + aaaaaa\n + aaaaaE\n + + Parameters + ========== + + start_x :int + x of start point + start_y :int + y of start point + end_x :int + x of end point + end_y :int + y of end point + + Returns + ======== + sum:int + sum of elements of rectangle that has up left corner of point(start_x , start_y ) and down right coener of (end_x , end_y) + + """ + return self._get_sum_to_origin(end_x ,end_y)- self._get_sum_to_origin(end_x , start_y-1 )\ + -self._get_sum_to_origin(start_x -1 ,end_y) + self._get_sum_to_origin(start_x-1 , start_y-1) + + +class BinaryIndexedTreeNd(object): + """ + ND Fenwick tree aka Binary Indexed Tree (BIT) + + Parameters + ============ + array :list + array of any dimintions. + backend: pydatastructs.Backend + The backend to be used. + Optional, by default, the best available + backend is used. + Examples + ============= + >>> from pydatastructs import BinaryIndexedTreeNd + >>> bit = BinaryIndexedTree([[1, 2, 3] ,\ + [2 , 3 ,4 ] ,\ + [8 ,6 ,7 ]]) + >>> bit.get_sum((1, 1) , (0 ,0) ) + 8 + >>> bit.add((1 ,1) , 3) + >>> bit.get_sum((1, 1) , (0 , 0) ) + 11 + + """ + __slots__ = ['tree' , 'array' , 'limits' ] + + def __new__(cls, array, **kwargs): + raise_if_backend_is_not_python(cls , kwargs.get('backend' , Backend.PYTHON)) + obj = object.__new__(cls) + current_dimension = array + obj.limits =[] + while type(current_dimension)==list or type(current_dimension)==tuple : + obj.limits.append(len(current_dimension)) + current_dimension = current_dimension[0] + + obj.array =[] + obj.array = obj._fillNdArray(0 , 0 , array) + obj.tree = obj._fillNdArray(0 , 0, None) + obj.init_sum() + return obj + @classmethod + def methods(cls): + return ['add','get_sum' ] + + + + def init_sum(self , ind =0 , curPosition = []): + if ind == len(self.limits): + self.add(tuple(curPosition) , self._get_element(tuple(curPosition) ,self.array)) + else : + for i in range(self.limits[ind]): + self.init_sum(ind+1 , curPosition + [i]) + + + + def _fillNdArray(self ,ind:int ,number:int , array:list , curPosition = [] )->list: + level = [] + if ind == len(self.limits): + level = number + if array != None : + level = self._get_element(tuple(curPosition) , array) + else : + for i in range(self.limits[ind]): + level.append(self._fillNdArray(ind+1 , number , array , curPosition +[i])) + + return level + + + + def _get_element(self , position:tuple , arr:list) : + ind = 0 + cur = arr + while ind < len(self.limits): + cur = cur[position[ind]] + ind+=1 + return cur + + + + def _add_to_element(self , val:int,position:tuple , arr:list): + ind =0 + cur = self.tree + while ind +1< len(self.limits): + cur = cur[position[ind]] + ind+=1 + cur[position[ind]] = cur[position[ind]] +val + + + def add(self , position:tuple , val:int , ind:int =0 , curPosition = []): + """ + + Parameters + ========== + position : tuple + position of value you want to add to. + + val :int + value you want to add. + + Returns + =========== + None + + """ + if ind == len(self.limits): + newPosition = [i-1 for i in curPosition] + self._add_to_element(val, tuple(newPosition), self.tree) + return + + i = position[ind] +1 + while i <= self.limits[ind] : + self.add(position , val , ind+1 , curPosition +[i]) + i += i &(-i) + + def _get_sum_to_origin(self , position:tuple , ind =0 , curPosition = [] ): + res =0 + if ind == len(self.limits): + newPosition = [(i-1) for i in curPosition] + res = self._get_element(tuple(newPosition) ,self.tree) + else : + i = position[ind]+1 + while i>0 : + res += self._get_sum_to_origin(position , ind+1 , curPosition+ [i]) + i-= i& (-i) + return res + + def _get_sum(self , start:tuple , end:tuple ,ind:int , numberOfElements:int , resPoint :list , sign:int ): + if ind == len(start) : + if numberOfElements != 0 : + return 0 + res = self._get_sum_to_origin(tuple(resPoint)) + return sign *(res) + else : + res =0 + if numberOfElements -1 >=0 : + res += self._get_sum(start ,end , ind+1 , numberOfElements -1 , resPoint +[end[ind] -1 ], sign ) + res += self._get_sum(start ,end , ind+1 , numberOfElements , resPoint+[start[ind] ] ,sign ) + return res + def get_sum(self , start:tuple , end:tuple ): + """ + + Parameters + ---------- + start:tuple + point near orignal point. + end : tuple + point far from orignal point. + Returns + ------- + sum :int + sum of element between two points. + + """ + res =0 + for i in range( 1 + len(self.limits)): + res += self._get_sum(start , end , 0 , i , [] , (-1)**i ) + return res + + + diff --git a/pydatastructs/trees/tests/test_binary_trees.py b/pydatastructs/trees/tests/test_binary_trees.py index ea9ea5bf3..31564ca93 100644 --- a/pydatastructs/trees/tests/test_binary_trees.py +++ b/pydatastructs/trees/tests/test_binary_trees.py @@ -1,8 +1,10 @@ from pydatastructs.trees.binary_trees import ( BinarySearchTree, BinaryTreeTraversal, AVLTree, - ArrayForTrees, BinaryIndexedTree, SelfBalancingBinaryTree, SplayTree, CartesianTree, Treap, RedBlackTree) + ArrayForTrees, BinaryIndexedTree, SelfBalancingBinaryTree, SplayTree, CartesianTree, Treap, RedBlackTree , ) from pydatastructs.utils.raises_util import raises from pydatastructs.utils.misc_util import TreeNode +from pydatastructs.trees.binary_trees import BinaryIndexedTree2D +from pydatastructs.trees.binary_trees import BinaryIndexedTreeNd from copy import deepcopy import random @@ -684,3 +686,106 @@ def test_RedBlackTree(): pre_order = trav.depth_first_search(order='pre_order') assert [node.key for node in in_order] == [2, 5, 6, 15, 20] assert [node.key for node in pre_order] == [6, 5, 2, 20, 15] + + +def test_BinaryIndexedTree2D(): + + n = random.randint(10, 40) + m = random.randint(10 ,40) + sample = [[random.randint(0, 10000) for i in range(m)] for j in range(n)] + + BIT = BinaryIndexedTree2D + bit2d = BIT(sample) + def sumRect(sample ,i , j , l ,k ): + sum =0 + for x in range (l, i +1 ): + for y in range (k, j+1 ): + sum += sample[x][y] + return sum + + for i in range(0 , len(sample)): + for j in range(0 , len(sample[0]) ): + for l in range (i , len(sample) ): + for k in range (j , len(sample[0])): + message = f"for (i , j ) = ({i} ,{j}) ,(l , k ) = ({l} ,{k})" + assert sumRect(sample , l , k , i , j ) == bit2d.get_area(i, j , l ,k ) , message + + for i in range(20) : + x = random.randint(0 , n-1) + y = random.randint(0 , m-1) + val = random.randint(0 , 100) + bit2d.add(x , y , val ) + sample[x][y] += val + + for i in range(0 , len(sample)): + for j in range(0 , len(sample[0]) ): + for l in range (i , len(sample) ): + for k in range (j , len(sample[0])): + message = f"for (i , j ) = ({i} ,{j}) ,(l , k ) = ({l} ,{k}) , After randoms updates" + assert sumRect(sample , l , k , i , j ) == bit2d.get_area(i, j , l ,k ) , message + + +def test_BinaryIndexedTreeNd(): + size = random.randint(1 , 4 ) + limits = [] + for i in range(size): + limits.append(random.randint(2 ,4 )) + + def makeArr(ind = 0 ): + if ind == size : + return random.randint(0 ,12) + else : + level = [] + for i in range(limits[ind]): + level.append(makeArr(ind +1 )) + return level + + arr =makeArr() + bits = BinaryIndexedTreeNd(arr) + assert arr == bits.array + + def getItem(ind , postion:tuple ): + ind = 0 + cur = arr + while ind <= len(postion) -1 : + cur = cur[postion[ind]] + ind+=1 + return cur + + def sum(ind , start:tuple , end:tuple ,position =[]): + if ind == size: + return getItem(0 , tuple(position)) + res =0 + for i in range(end[ind] , start[ind] +1 ): + res += sum(ind +1 , start , end , position +[i]) + return res + + def traverse(ind = 0 , position = []): + if ind == size : + f = lambda x : random.randint(0 , x ) + end = [f(position[i]) for i in range(size)] + message = f"{tuple(position)} and {tuple(end)}" + assert bits.get_sum(tuple(position) , tuple(end) ) == sum(0 , tuple(position) , tuple(end)) , message + assert bits.get_sum(tuple(position) , tuple(position)) == getItem(0 , tuple(position)) + else: + for i in range(limits[ind]): + traverse(ind+1 , position + [i]) + traverse() + def add(ind , position:tuple , val): + ind =0 + cur = arr + while ind +1 < size : + cur = cur[position[ind]] + ind +=1 + cur[position[ind]] += val + + for _ in range(0 ,10 ): + position =[] + for i in range(size): + position.append(random.randint(0, limits[i]-1 )) + bits.add(position , 10 ) + add(0 ,position , 10) + + traverse() + +