Skip to content

Commit 50804c4

Browse files
committed
added feature: fenwich tree
1 parent f9ed074 commit 50804c4

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

pydatastructs/trees/fenwich_tree.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
class fenwich_tree:
2+
"""
3+
Implementation of Fenwich tree/Binary Indexed Tree
4+
"""
5+
6+
def __init__(self, size_or_array):
7+
"""
8+
Initializes the Fenwich Tree.
9+
10+
Args:
11+
size_or_array: size of array the tree will represent or array of values
12+
"""
13+
14+
if isinstance(size_or_array, int):
15+
self.size = size_or_array
16+
self.tree = [0] * (self.size + 1)
17+
self.original_array = [0] * self.size
18+
elif isinstance(size_or_array, list):
19+
self.original_array = list(size_or_array)
20+
self.size = len(self.original_array)
21+
self.tree = [0] * (self.size + 1)
22+
for i, val in enumerate(self.original_array):
23+
self._update_tree(i, val)
24+
else:
25+
raise ValueError("size_or_array must be an integer or a list.")
26+
27+
def _update_tree(self, index, delta):
28+
"""
29+
Internal helper to update the Fenwick Tree after a change in the original array.
30+
"""
31+
index += 1 # Fenwick Tree is 1-indexed
32+
while index <= self.size:
33+
self.tree[index] += delta
34+
index += index & (-index)
35+
36+
def update(self, index, value):
37+
"""
38+
Updates the value at the given index in the original array and the Fenwick Tree.
39+
40+
Args:
41+
index: The index to update (0-based).
42+
value: The new value.
43+
"""
44+
if not (0 <= index < self.size):
45+
raise IndexError("Index out of bounds")
46+
delta = value - self.original_array[index]
47+
self.original_array[index] = value
48+
self._update_tree(index, delta)
49+
50+
def prefix_sum(self, index):
51+
"""
52+
Calculates the prefix sum up to the given index (inclusive).
53+
54+
Args:
55+
index: The index up to which to calculate the sum (0-based).
56+
57+
Returns:
58+
The prefix sum.
59+
"""
60+
if not (0 <= index < self.size):
61+
raise IndexError("Index out of bounds")
62+
index += 1 #
63+
sum_val = 0
64+
while index > 0:
65+
sum_val += self.tree[index]
66+
index -= index & (-index)
67+
return sum_val
68+
69+
def range_sum(self, start_index, end_index):
70+
"""
71+
Calculates the sum of elements within the given range (inclusive).
72+
73+
Args:
74+
start_index: The starting index of the range (0-based).
75+
end_index: The ending index of the range (0-based).
76+
77+
Returns:
78+
The sum of elements in the range.
79+
"""
80+
if not (0 <= start_index <= end_index < self.size):
81+
raise IndexError("Indices out of bounds")
82+
if start_index == 0:
83+
return self.prefix_sum(end_index)
84+
else:
85+
return self.prefix_sum(end_index) - self.prefix_sum(start_index - 1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import unittest
2+
from pydatastructs import fenwich_tree
3+
4+
class TestFenwickTree(unittest.TestCase):
5+
6+
def test_initialization_with_size(self):
7+
ft = fenwich_tree(5)
8+
self.assertEqual(ft.size, 5)
9+
self.assertEqual(ft.tree, [0, 0, 0, 0, 0, 0])
10+
self.assertEqual(ft.original_array, [0, 0, 0, 0, 0])
11+
12+
def test_initialization_with_array(self):
13+
arr = [1, 2, 3, 4, 5]
14+
ft = fenwich_tree(arr)
15+
self.assertEqual(ft.size, 5)
16+
self.assertEqual(ft.original_array, arr)
17+
# Manually calculate prefix sums and check the tree structure
18+
expected_tree = [0, 1, 3, 3, 10, 5]
19+
self.assertEqual(ft.tree, expected_tree)
20+
21+
def test_initialization_with_empty_array(self):
22+
arr = []
23+
ft = fenwich_tree(arr)
24+
self.assertEqual(ft.size, 0)
25+
self.assertEqual(ft.tree, [0])
26+
self.assertEqual(ft.original_array, [])
27+
28+
def test_initialization_with_invalid_input(self):
29+
with self.assertRaises(ValueError):
30+
fenwich_tree("invalid")
31+
32+
def test_update_single_element(self):
33+
ft = fenwich_tree([1, 2, 3, 4, 5])
34+
ft.update(1, 10)
35+
self.assertEqual(ft.original_array, [1, 10, 3, 4, 5])
36+
expected_tree = [0, 1, 11, 3, 18, 5]
37+
self.assertEqual(ft.tree, expected_tree)
38+
39+
def test_update_out_of_bounds(self):
40+
ft = fenwich_tree(5)
41+
with self.assertRaises(IndexError):
42+
ft.update(5, 10)
43+
with self.assertRaises(IndexError):
44+
ft.update(-1, 10)
45+
46+
def test_prefix_sum_positive_indices(self):
47+
arr = [1, 2, 3, 4, 5]
48+
ft = fenwich_tree(arr)
49+
self.assertEqual(ft.prefix_sum(0), 1)
50+
self.assertEqual(ft.prefix_sum(1), 3)
51+
self.assertEqual(ft.prefix_sum(2), 6)
52+
self.assertEqual(ft.prefix_sum(3), 10)
53+
self.assertEqual(ft.prefix_sum(4), 15)
54+
55+
def test_prefix_sum_out_of_bounds(self):
56+
ft = fenwich_tree(5)
57+
with self.assertRaises(IndexError):
58+
ft.prefix_sum(5)
59+
with self.assertRaises(IndexError):
60+
ft.prefix_sum(-1)
61+
62+
def test_prefix_sum_empty_array(self):
63+
ft = fenwich_tree([])
64+
with self.assertRaises(IndexError):
65+
ft.prefix_sum(0) # Should raise IndexError as size is 0
66+
67+
def test_range_sum_valid_range(self):
68+
arr = [1, 2, 3, 4, 5]
69+
ft = fenwich_tree(arr)
70+
self.assertEqual(ft.range_sum(0, 0), 1)
71+
self.assertEqual(ft.range_sum(0, 1), 3)
72+
self.assertEqual(ft.range_sum(1, 3), 2 + 3 + 4)
73+
self.assertEqual(ft.range_sum(2, 4), 3 + 4 + 5)
74+
self.assertEqual(ft.range_sum(0, 4), 1 + 2 + 3 + 4 + 5)
75+
76+
def test_range_sum_out_of_bounds(self):
77+
ft = fenwich_tree(5)
78+
with self.assertRaises(IndexError):
79+
ft.range_sum(0, 5)
80+
with self.assertRaises(IndexError):
81+
ft.range_sum(-1, 2)
82+
with self.assertRaises(IndexError):
83+
ft.range_sum(1, 5)
84+
with self.assertRaises(IndexError):
85+
ft.range_sum(-1, -1)
86+
87+
def test_range_sum_invalid_range(self):
88+
ft = fenwich_tree(5)
89+
with self.assertRaises(IndexError):
90+
ft.range_sum(3, 1)
91+
92+
def test_range_sum_single_element(self):
93+
arr = [10, 20, 30]
94+
ft = fenwich_tree(arr)
95+
self.assertEqual(ft.range_sum(0, 0), 10)
96+
self.assertEqual(ft.range_sum(1, 1), 20)
97+
self.assertEqual(ft.range_sum(2, 2), 30)
98+
99+
def test_range_sum_entire_array(self):
100+
arr = [1, 2, 3, 4, 5]
101+
ft = fenwich_tree(arr)
102+
self.assertEqual(ft.range_sum(0, ft.size - 1), 15)
103+
104+
def test_update_and_query_sequence(self):
105+
ft = fenwich_tree([2, 5, 1, 8, 3])
106+
self.assertEqual(ft.prefix_sum(3), 2 + 5 + 1 + 8) # 16
107+
ft.update(1, 10)
108+
self.assertEqual(ft.prefix_sum(3), 2 + 10 + 1 + 8) # 21
109+
self.assertEqual(ft.range_sum(0, 2), 2 + 10 + 1) # 13
110+
ft.update(4, 0)
111+
self.assertEqual(ft.prefix_sum(4), 2 + 10 + 1 + 8 + 0) # 21
112+
self.assertEqual(ft.range_sum(3, 4), 8 + 0) # 8

0 commit comments

Comments
 (0)