Skip to content

Commit 5d20dbf

Browse files
SaurabhGoyalcclauss
authored andcommitted
add a generic heap (#906)
* add a generic heap * Delete __init__.py * Rename data_structures/Heap/heap_generic.py to data_structures/heap/heap_generic.py * Add doctests * Fix doctests * Fix doctests again
1 parent 2fb6f78 commit 5d20dbf

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
class Heap(object):
2+
"""A generic Heap class, can be used as min or max by passing the key function accordingly.
3+
"""
4+
5+
def __init__(self, key=None):
6+
# Stores actual heap items.
7+
self.arr = list()
8+
# Stores indexes of each item for supporting updates and deletion.
9+
self.pos_map = {}
10+
# Stores current size of heap.
11+
self.size = 0
12+
# Stores function used to evaluate the score of an item on which basis ordering will be done.
13+
self.key = key or (lambda x: x)
14+
15+
def _parent(self, i):
16+
"""Returns parent index of given index if exists else None"""
17+
return int((i - 1) / 2) if i > 0 else None
18+
19+
def _left(self, i):
20+
"""Returns left-child-index of given index if exists else None"""
21+
left = int(2 * i + 1)
22+
return left if 0 < left < self.size else None
23+
24+
def _right(self, i):
25+
"""Returns right-child-index of given index if exists else None"""
26+
right = int(2 * i + 2)
27+
return right if 0 < right < self.size else None
28+
29+
def _swap(self, i, j):
30+
"""Performs changes required for swapping two elements in the heap"""
31+
# First update the indexes of the items in index map.
32+
self.pos_map[self.arr[i][0]], self.pos_map[self.arr[j][0]] = (
33+
self.pos_map[self.arr[j][0]], self.pos_map[self.arr[i][0]]
34+
)
35+
# Then swap the items in the list.
36+
self.arr[i], self.arr[j] = self.arr[j], self.arr[i]
37+
38+
def _cmp(self, i, j):
39+
"""Compares the two items using default comparison"""
40+
return self.arr[i][1] < self.arr[j][1]
41+
42+
def _get_valid_parent(self, i):
43+
"""Returns index of valid parent as per desired ordering among given index and both it's children"""
44+
left = self._left(i)
45+
right = self._right(i)
46+
valid_parent = i
47+
48+
if left is not None and not self._cmp(left, valid_parent):
49+
valid_parent = left
50+
if right is not None and not self._cmp(right, valid_parent):
51+
valid_parent = right
52+
53+
return valid_parent
54+
55+
def _heapify_up(self, index):
56+
"""Fixes the heap in upward direction of given index"""
57+
parent = self._parent(index)
58+
while parent is not None and not self._cmp(index, parent):
59+
self._swap(index, parent)
60+
index, parent = parent, self._parent(parent)
61+
62+
def _heapify_down(self, index):
63+
"""Fixes the heap in downward direction of given index"""
64+
valid_parent = self._get_valid_parent(index)
65+
while valid_parent != index:
66+
self._swap(index, valid_parent)
67+
index, valid_parent = valid_parent, self._get_valid_parent(valid_parent)
68+
69+
def update_item(self, item, item_value):
70+
"""Updates given item value in heap if present"""
71+
if item not in self.pos_map:
72+
return
73+
index = self.pos_map[item]
74+
self.arr[index] = [item, self.key(item_value)]
75+
# Make sure heap is right in both up and down direction.
76+
# Ideally only one of them will make any change.
77+
self._heapify_up(index)
78+
self._heapify_down(index)
79+
80+
def delete_item(self, item):
81+
"""Deletes given item from heap if present"""
82+
if item not in self.pos_map:
83+
return
84+
index = self.pos_map[item]
85+
del self.pos_map[item]
86+
self.arr[index] = self.arr[self.size - 1]
87+
self.pos_map[self.arr[self.size - 1][0]] = index
88+
self.size -= 1
89+
# Make sure heap is right in both up and down direction.
90+
# Ideally only one of them will make any change- so no performance loss in calling both.
91+
if self.size > index:
92+
self._heapify_up(index)
93+
self._heapify_down(index)
94+
95+
def insert_item(self, item, item_value):
96+
"""Inserts given item with given value in heap"""
97+
arr_len = len(self.arr)
98+
if arr_len == self.size:
99+
self.arr.append([item, self.key(item_value)])
100+
else:
101+
self.arr[self.size] = [item, self.key(item_value)]
102+
self.pos_map[item] = self.size
103+
self.size += 1
104+
self._heapify_up(self.size - 1)
105+
106+
def get_top(self):
107+
"""Returns top item tuple (Calculated value, item) from heap if present"""
108+
return self.arr[0] if self.size else None
109+
110+
def extract_top(self):
111+
"""Returns top item tuple (Calculated value, item) from heap and removes it as well if present"""
112+
top_item_tuple = self.get_top()
113+
if top_item_tuple:
114+
self.delete_item(top_item_tuple[0])
115+
return top_item_tuple
116+
117+
118+
def test_heap() -> None:
119+
"""
120+
>>> h = Heap() # Max-heap
121+
>>> h.insert_item(5, 34)
122+
>>> h.insert_item(6, 31)
123+
>>> h.insert_item(7, 37)
124+
>>> h.get_top()
125+
[7, 37]
126+
>>> h.extract_top()
127+
[7, 37]
128+
>>> h.extract_top()
129+
[5, 34]
130+
>>> h.extract_top()
131+
[6, 31]
132+
>>> h = Heap(key=lambda x: -x) # Min heap
133+
>>> h.insert_item(5, 34)
134+
>>> h.insert_item(6, 31)
135+
>>> h.insert_item(7, 37)
136+
>>> h.get_top()
137+
[6, -31]
138+
>>> h.extract_top()
139+
[6, -31]
140+
>>> h.extract_top()
141+
[5, -34]
142+
>>> h.extract_top()
143+
[7, -37]
144+
>>> h.insert_item(8, 45)
145+
>>> h.insert_item(9, 40)
146+
>>> h.insert_item(10, 50)
147+
>>> h.get_top()
148+
[9, -40]
149+
>>> h.update_item(10, 30)
150+
>>> h.get_top()
151+
[10, -30]
152+
>>> h.delete_item(10)
153+
>>> h.get_top()
154+
[9, -40]
155+
"""
156+
pass
157+
158+
159+
if __name__ == "__main__":
160+
import doctest
161+
162+
doctest.testmod()

0 commit comments

Comments
 (0)