|
1 | | -from typing import Any, Dict, Generic, Hashable, List, Optional, Protocol, TypeVar |
| 1 | +from typing import Any, Dict, Hashable, Iterator, List, MutableMapping, Optional, Protocol, Tuple, TypeVar |
2 | 2 |
|
3 | 3 |
|
4 | 4 | class ComparableP(Protocol): |
5 | 5 | def __lt__(self, other: Any) -> bool: ... |
6 | 6 | def __eq__(self, other: Any) -> bool: ... |
7 | 7 |
|
8 | 8 |
|
9 | | -class ComparableAndHashable(ComparableP, Protocol, Hashable): |
10 | | - pass |
| 9 | +Key = TypeVar('Key', bound=Hashable) |
| 10 | +Value = TypeVar('Value', bound=ComparableP) |
11 | 11 |
|
12 | 12 |
|
13 | | -Item = TypeVar('Item', bound=ComparableAndHashable) |
14 | | - |
15 | | - |
16 | | -class RankMap(Generic[Item]): |
| 13 | +class RankMap(MutableMapping[Key, Value]): |
17 | 14 | """ |
18 | | - Extended heap data structure implementation. |
19 | | - Similar to `heapq` but supports remove and replace operations. |
20 | | - To support remove and replace operations items must be unique. |
| 15 | + A hash-map / binary-heap combined data structure that additionally supports the following operations: |
| 16 | + - top: get the element with the lowest value in O(1) |
| 17 | + - next: removes the element with the lowest value in O(log(n)) |
21 | 18 | """ |
22 | 19 |
|
23 | 20 | def __init__(self) -> None: |
24 | | - self._heap: List[Item] = [] |
25 | | - self._index: Dict[Item, int] = {} |
| 21 | + self._heap: List[Tuple[Value, Key]] = [] |
| 22 | + self._index: Dict[Key, int] = {} |
26 | 23 |
|
27 | 24 | def __len__(self) -> int: |
28 | 25 | return len(self._heap) |
29 | 26 |
|
30 | 27 | def __bool__(self) -> bool: |
31 | 28 | return bool(self._heap) |
32 | 29 |
|
33 | | - def __contains__(self, item: Item) -> bool: |
34 | | - return item in self._index |
| 30 | + def __getitem__(self, key: Key) -> Value: |
| 31 | + idx = self._index[key] |
| 32 | + value, key = self._heap[idx] |
| 33 | + |
| 34 | + return value |
| 35 | + |
| 36 | + def __setitem__(self, key: Key, value: Value) -> None: |
| 37 | + self.insert_or_replace(key, value) |
| 38 | + |
| 39 | + def __delitem__(self, key: Key) -> None: |
| 40 | + self.remove(key) |
| 41 | + |
| 42 | + def __iter__(self) -> Iterator[Key]: |
| 43 | + return (key for value, key in self._heap) |
35 | 44 |
|
36 | 45 | def clear(self) -> None: |
37 | 46 | """ |
38 | | - Remove all items from the heap. |
| 47 | + Remove all items from the map. |
39 | 48 | """ |
40 | 49 |
|
41 | 50 | self._heap.clear() |
42 | 51 | self._index.clear() |
43 | 52 |
|
44 | | - def insert(self, item: Item) -> None: |
| 53 | + def insert(self, key: Key, value: Value) -> None: |
45 | 54 | """ |
46 | | - Inserts an item onto the heap, maintaining the heap invariant. |
47 | | - If the item already presented raises `KeyError`. |
| 55 | + Inserts an item into the map. |
| 56 | + If an item with the provided key already presented raises `KeyError`. |
48 | 57 |
|
49 | | - :param item: item to be pushed |
| 58 | + :param key: item key |
| 59 | + :param value: item value |
50 | 60 | """ |
51 | 61 |
|
52 | | - if item in self._index: |
53 | | - raise KeyError("item already exists") |
| 62 | + if key in self._index: |
| 63 | + raise KeyError("key already exists") |
54 | 64 |
|
55 | 65 | item_idx = len(self._heap) |
56 | | - self._heap.append(item) |
57 | | - self._index[item] = item_idx |
| 66 | + self._heap.append((value, key)) |
| 67 | + self._index[key] = item_idx |
58 | 68 |
|
59 | 69 | self._siftdown(item_idx) |
60 | 70 |
|
61 | | - def insert_or_replace(self, item: Item) -> None: |
| 71 | + def insert_or_replace(self, key: Key, value: Value) -> Optional[Value]: |
62 | 72 | """ |
63 | | - Inserts an item onto the heap or replaces it if it already exists. |
| 73 | + Inserts an item into the map or replaces it if it already exists. |
| 74 | +
|
| 75 | + :param key: item key |
| 76 | + :param value: item value |
| 77 | + :return: previous item or `None` |
64 | 78 | """ |
65 | 79 |
|
66 | | - if item in self._index: |
67 | | - self.remove(item) |
| 80 | + if key in self._index: |
| 81 | + prev = self.remove(key) |
| 82 | + else: |
| 83 | + prev = None |
| 84 | + |
| 85 | + self.insert(key, value) |
68 | 86 |
|
69 | | - self.insert(item) |
| 87 | + return prev |
70 | 88 |
|
71 | | - def pop(self) -> Optional[Item]: |
| 89 | + def next(self) -> Optional[Value]: |
72 | 90 | """ |
73 | | - Pops the smallest item off the heap, maintaining the heap invariant. |
| 91 | + Pops the smallest item from the map. |
74 | 92 |
|
75 | 93 | :return: the smallest item |
76 | 94 | """ |
77 | 95 |
|
78 | 96 | if len(self._heap) == 0: |
79 | 97 | return None |
80 | 98 |
|
81 | | - last_item = self._heap.pop() |
82 | | - self._index.pop(last_item) |
| 99 | + value, key = self._heap[0] |
| 100 | + return self.remove(key) |
83 | 101 |
|
84 | | - if self._heap: |
85 | | - first_item = self._heap[0] |
86 | | - self._index.pop(first_item) |
87 | | - self._heap[0] = last_item |
88 | | - self._index[last_item] = 0 |
89 | | - self._siftup(0) |
90 | | - return first_item |
| 102 | + def top(self) -> Optional[Value]: |
| 103 | + """ |
| 104 | + Returns the smallest item from the map. |
91 | 105 |
|
92 | | - else: |
93 | | - return last_item |
| 106 | + :return: the smallest item |
| 107 | + """ |
94 | 108 |
|
95 | | - def top(self) -> Optional[Item]: |
96 | 109 | if len(self._heap) != 0: |
97 | | - return self._heap[0] |
| 110 | + first_value, first_key = self._heap[0] |
| 111 | + return first_value |
98 | 112 | else: |
99 | 113 | return None |
100 | 114 |
|
101 | | - def remove(self, item: Item) -> None: |
| 115 | + def remove(self, key: Key) -> Optional[Value]: |
102 | 116 | """ |
103 | | - Removes an item from the heap. |
| 117 | + Removes an item from the map |
104 | 118 |
|
105 | | - :param item: item to be removed |
| 119 | + :param key: item key |
| 120 | + :returns: removed item value |
106 | 121 | """ |
107 | 122 |
|
108 | | - if item not in self._index: |
109 | | - return |
110 | | - |
111 | | - idx = self._index.pop(item) |
112 | | - if idx == len(self._heap) - 1: |
113 | | - self._heap.pop() |
114 | | - else: |
115 | | - last_item = self._heap[-1] |
116 | | - self._heap[idx] = last_item |
117 | | - self._heap.pop() |
118 | | - self._index[last_item] = idx |
119 | | - self._siftup(idx) |
| 123 | + if (idx := self._index.get(key)) is None: |
| 124 | + return None |
120 | 125 |
|
121 | | - def replace(self, old_item: Item, new_item: Item) -> None: |
122 | | - """ |
123 | | - Replaces an item with the new one, maintaining the heap invariant. |
124 | | - If the old_item not presented raises `KeyError`. |
125 | | - If the new_item already presented raises `KeyError`. |
| 126 | + self._swap_heap_elements(idx, len(self._heap) - 1) |
126 | 127 |
|
127 | | - :param old_item: item to be replaces |
128 | | - :param new_item: item the old one is replaced by |
129 | | - """ |
| 128 | + value, key = self._heap.pop() |
| 129 | + self._index.pop(key) |
130 | 130 |
|
131 | | - if old_item not in self._index: |
132 | | - raise KeyError("item not found") |
| 131 | + if idx < len(self._heap): |
| 132 | + self._siftdown(idx) |
| 133 | + self._siftup(idx) |
133 | 134 |
|
134 | | - if new_item in self._index: |
135 | | - raise KeyError("item already exists") |
| 135 | + return value |
136 | 136 |
|
137 | | - old_idx = self._index.pop(old_item) |
138 | | - self._heap[old_idx] = new_item |
139 | | - self._index[new_item] = old_idx |
| 137 | + def _swap_heap_elements(self, idx1: int, idx2: int) -> None: |
| 138 | + key1 = self._heap[idx1][1] |
| 139 | + key2 = self._heap[idx2][1] |
140 | 140 |
|
141 | | - if new_item < old_item: |
142 | | - self._siftdown(old_idx) |
143 | | - else: |
144 | | - self._siftup(old_idx) |
| 141 | + self._heap[idx1], self._heap[idx2] = self._heap[idx2], self._heap[idx1] |
| 142 | + self._index[key1] = idx2 |
| 143 | + self._index[key2] = idx1 |
145 | 144 |
|
146 | 145 | def _siftup(self, idx: int) -> None: |
147 | | - item = self._heap[idx] |
| 146 | + value, key = self._heap[idx] |
148 | 147 |
|
149 | 148 | left_child_idx = 2 * idx + 1 |
150 | 149 | while left_child_idx < len(self._heap): |
151 | 150 | right_child_idx = left_child_idx + 1 |
152 | | - if right_child_idx >= len(self._heap) or self._heap[left_child_idx] < self._heap[right_child_idx]: |
| 151 | + if right_child_idx >= len(self._heap) or self._heap[left_child_idx][0] < self._heap[right_child_idx][0]: |
153 | 152 | min_child_idx = left_child_idx |
154 | 153 | else: |
155 | 154 | min_child_idx = right_child_idx |
156 | 155 |
|
157 | | - child = self._heap[min_child_idx] |
158 | | - if item < child or item == child: |
| 156 | + child_value, child_key = self._heap[min_child_idx] |
| 157 | + if value < child_value or value == child_value: |
159 | 158 | break |
160 | 159 |
|
161 | | - self._heap[idx] = child |
162 | | - self._index[child] = idx |
| 160 | + self._heap[idx] = child_value, child_key |
| 161 | + self._index[child_key] = idx |
163 | 162 |
|
164 | 163 | idx = min_child_idx |
165 | 164 | left_child_idx = 2 * idx + 1 |
166 | 165 |
|
167 | | - self._heap[idx] = item |
168 | | - self._index[item] = idx |
| 166 | + self._heap[idx] = value, key |
| 167 | + self._index[key] = idx |
169 | 168 |
|
170 | 169 | def _siftdown(self, idx: int) -> None: |
171 | | - item = self._heap[idx] |
| 170 | + value, key = self._heap[idx] |
172 | 171 |
|
173 | 172 | while idx > 0: |
174 | 173 | parent_idx = (idx - 1) // 2 |
175 | | - parent = self._heap[parent_idx] |
176 | | - if item < parent: |
177 | | - self._heap[idx] = parent |
178 | | - self._index[parent] = idx |
| 174 | + parent_value, parent_key = self._heap[parent_idx] |
| 175 | + if value < parent_value: |
| 176 | + self._heap[idx] = parent_value, parent_key |
| 177 | + self._index[parent_key] = idx |
179 | 178 | idx = parent_idx |
180 | 179 | else: |
181 | 180 | break |
182 | 181 |
|
183 | | - self._heap[idx] = item |
184 | | - self._index[item] = idx |
| 182 | + self._heap[idx] = value, key |
| 183 | + self._index[key] = idx |
0 commit comments