Skip to content

Commit 200d28f

Browse files
committed
feature:Added IntervalTree DataStructure
1 parent a21d00e commit 200d28f

File tree

5 files changed

+354
-3
lines changed

5 files changed

+354
-3
lines changed

pydatastructs/trees/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
binary_trees,
55
m_ary_trees,
66
space_partitioning_trees,
7-
heaps
7+
heaps,
8+
intervaltree
89
)
910

1011
from .binary_trees import (
@@ -38,3 +39,9 @@
3839
BinomialHeap
3940
)
4041
__all__.extend(heaps.__all__)
42+
43+
from .intervaltree import (
44+
Node,
45+
IntervalTree
46+
)
47+
__all__.extend(intervaltree.__all__)

pydatastructs/trees/intervaltree.py

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
__all__ = [
2+
'Node',
3+
'IntervalTree'
4+
]
5+
6+
7+
class Node(object):
8+
"""
9+
Abstract interval tree node.
10+
11+
Parameters
12+
==========
13+
14+
interval
15+
Required, An interval which is represented
16+
as a pair in tuple (low, high).
17+
18+
"""
19+
def __init__(self, interval):
20+
self.interval = interval
21+
self.left_child = None
22+
self.right_child = None
23+
self.Max = None
24+
25+
def hasChild(self):
26+
"""
27+
Checks if Node has child or not.
28+
29+
Returns
30+
=======
31+
32+
True
33+
If the node has a left child or right child or both.
34+
35+
False
36+
If the node does not has any child.
37+
38+
"""
39+
if self.left_child or self.right_child:
40+
return True
41+
return False
42+
43+
def maxOfChild(self):
44+
"""
45+
Returns interval with maximum high value in subtree
46+
rooted with this node
47+
48+
Returns
49+
=======
50+
51+
interval
52+
interval with maximum high value in subtree rooted with this node.
53+
"""
54+
child_interval = list()
55+
if(self.left_child):
56+
child_interval.append(self.left_child.interval)
57+
if(self.right_child):
58+
child_interval.append(self.right_child.interval)
59+
return max(child_interval)
60+
61+
62+
class IntervalTree(object):
63+
"""
64+
Abstract binary tree.
65+
66+
Parameters
67+
==========
68+
69+
key
70+
Required, root node of the tree.
71+
72+
"""
73+
def __init__(self, root):
74+
self.root = root
75+
76+
def addNode(self, new_node):
77+
"""
78+
Adds a new node to the tree.
79+
80+
Parameters
81+
==========
82+
83+
Node
84+
Required, new node to be added in tree.
85+
86+
Returns
87+
=======
88+
89+
None
90+
91+
"""
92+
node = self.root
93+
while(node is not None):
94+
if(new_node.interval[0] <= node.interval[0]):
95+
if(node.left_child is None):
96+
node.left_child = new_node
97+
return
98+
node = node.left_child
99+
else:
100+
if(node.right_child is None):
101+
node.right_child = new_node
102+
return
103+
node = node.right_child
104+
105+
def searchIntervalOverlap(self, query_node):
106+
"""
107+
A utility function to search if any node overlaps with the given Node.
108+
109+
Parameters
110+
==========
111+
112+
query_node
113+
Required, Node to be searched for overlapping.
114+
115+
Returns
116+
=======
117+
118+
Node, Node, boolean
119+
120+
Node:
121+
parent_node
122+
Parent Node of the child node to be deleted.
123+
None
124+
if given interval node does not overlap with another node.
125+
126+
Node:
127+
child_node
128+
Child node to be deleted.
129+
None
130+
if given interval node does not overlap with another node.
131+
132+
boolean:
133+
True
134+
if given interval node overlaps with another node.
135+
False
136+
if given interval node does not overlap with another node
137+
138+
"""
139+
p_node = None
140+
c_node = self.root
141+
while(c_node):
142+
if(self.isOverlapping(c_node.interval, query_node)):
143+
# print("Overlapping with ", c_node.interval)
144+
return p_node, c_node, True
145+
else:
146+
p_node = c_node
147+
if(c_node.Max >= query_node[0]):
148+
c_node = c_node.left_child
149+
else:
150+
c_node = c_node.right_child
151+
return None, None, False
152+
153+
def isOverlapping(self, interval_left, interval_right):
154+
"""
155+
A utility function to check if given two nodes
156+
overlaps with each other.
157+
158+
Parameters
159+
==========
160+
161+
interval_left
162+
Required, left node to be searched for overlapping.
163+
164+
interval_right
165+
Required, right node to be searched for overlapping.
166+
167+
168+
Returns
169+
=======
170+
True
171+
if the two given nodes overlap with each other
172+
173+
False
174+
if the two given nodes does not overlap with each other
175+
176+
"""
177+
if((interval_left[0] <= interval_right[1]) and
178+
(interval_right[0] <= interval_left[1])):
179+
return True
180+
181+
return False
182+
183+
def maxOfSubtree(self, root_node):
184+
"""
185+
A utility function to set root_node.Max as the maximum
186+
high value in subtree rooted with this node
187+
188+
Parameters
189+
==========
190+
191+
root_node
192+
Root Node of the subtree to be searched for maximum value
193+
194+
Returns
195+
=======
196+
197+
None
198+
"""
199+
if((not root_node.Max) and (root_node.hasChild())):
200+
max_array = []
201+
if(root_node.left_child):
202+
self.maxOfSubtree(root_node.left_child)
203+
max_array.append(root_node.left_child.Max)
204+
if(root_node.right_child):
205+
self.maxOfSubtree(root_node.right_child)
206+
max_array.append(root_node.right_child.Max)
207+
max_array.append(root_node.interval[1])
208+
root_node.Max = max(max_array)
209+
return
210+
211+
else:
212+
root_node.Max = root_node.interval[1]
213+
return
214+
215+
def constructMax(self):
216+
"""
217+
Sets root.Max as the maximum high value in whole tree.
218+
219+
"""
220+
node = self.root
221+
self.maxOfSubtree(node)
222+
223+
def printTree(self):
224+
"""
225+
A utility function to print the Interval Tree
226+
227+
"""
228+
node_list = [self.root]
229+
it_print = []
230+
while(len(node_list) != 0):
231+
current_node = node_list[0]
232+
node_list.pop(0)
233+
it_print.append((current_node.interval, current_node.Max))
234+
# print(current_node.interval, current_node.Max)
235+
if(current_node.left_child is not None):
236+
node_list.append(current_node.left_child)
237+
if(current_node.right_child is not None):
238+
node_list.append(current_node.right_child)
239+
return it_print
240+
241+
def delete_node(self, node_):
242+
"""
243+
Deletes a given node from the tree
244+
245+
Parameters
246+
==========
247+
248+
Node
249+
Required, node to be deleted from the tree.
250+
251+
Returns
252+
=======
253+
254+
True
255+
if interval node is present in tree and overlaps with another node.
256+
257+
False
258+
if interval node does not overlap with any node.
259+
260+
"""
261+
parent_node, node_to_delete, _ = self.searchIntervalOverlap(node_)
262+
263+
# If no overlap
264+
if not node_to_delete:
265+
return False
266+
267+
if node_to_delete.hasChild():
268+
if node_to_delete.left_child:
269+
if self.whichChild(parent_node, node_to_delete) == "left":
270+
parent_node.left_child = node_to_delete.left_child
271+
else:
272+
parent_node.right_child = node_to_delete.left_child
273+
self.reloadTree(node_to_delete.right_child)
274+
else:
275+
if self.whichChild(parent_node, node_to_delete) == "left":
276+
parent_node.left_child = node_to_delete.right_child
277+
else:
278+
parent_node.right_child = node_to_delete.right_child
279+
else:
280+
if parent_node.left_child == node_to_delete:
281+
parent_node.left_child = None
282+
if parent_node.right_child == node_to_delete:
283+
parent_node.right_child = None
284+
return True
285+
286+
def whichChild(self, p_node, c_node):
287+
"""
288+
A utility function to check whether the given child node is
289+
left child or right child of the given parent node.
290+
291+
Parameters
292+
==========
293+
294+
p_node
295+
Required, parent node.
296+
297+
c_node
298+
Required, child node.
299+
300+
Returns
301+
=======
302+
303+
"left"
304+
if child node is left child of parent node.
305+
306+
"right"
307+
if child node is right child of parent node.
308+
309+
"""
310+
if p_node.left_child == c_node:
311+
return "left"
312+
if p_node.right_child == c_node:
313+
return "right"
314+
315+
def reloadTree(self, node_):
316+
if node_.hasChild():
317+
if node_.left_child:
318+
reloadTree(node_.left_child)
319+
if node_.right_child:
320+
reloadTree(node_.left_child)
321+
else:
322+
self.addNode(node_)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from pydatastructs.trees.intervaltree import (Node, IntervalTree)
2+
3+
def test_IntervalTree():
4+
it = IntervalTree(Node([15, 20]))
5+
it.addNode(Node([10, 30]))
6+
it.addNode(Node([17, 19]))
7+
it.addNode(Node([5, 20]))
8+
it.addNode(Node([12, 15]))
9+
it.addNode(Node([30, 40]))
10+
it.constructMax()
11+
# Explicit check for the printTree() method of IntervalTree Class
12+
assert it.printTree() == [([15, 20], 40), ([10, 30], 30), ([17, 19], 40), ([5, 20], 20), ([12, 15], 15), ([30, 40], 40)]
13+
p_node, c_node, ret = it.searchIntervalOverlap([6, 7])
14+
assert p_node.interval == [10, 30]
15+
assert c_node.interval == [5, 20]
16+
assert ret is True
17+
p_node, c_node, ret = it.searchIntervalOverlap([-1, -2])
18+
assert p_node is None
19+
assert c_node is None
20+
assert ret is False
21+
assert it.delete_node([9, 11]) is None
22+
assert it.printTree() == [([15, 20], 40), ([5, 20], 20), ([17, 19], 40), ([12, 15], 15), ([30, 40], 40)]

pydatastructs/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
CartesianTreeNode,
1414
RedBlackTreeNode,
1515
TrieNode,
16-
SkipNode
16+
SkipNode,
1717
)
1818
__all__.extend(misc_util.__all__)

pydatastructs/utils/misc_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'CartesianTreeNode',
1111
'RedBlackTreeNode',
1212
'TrieNode',
13-
'SkipNode'
13+
'SkipNode',
1414
]
1515

1616
_check_type = lambda a, t: isinstance(a, t)

0 commit comments

Comments
 (0)