|
| 1 | +""" |
| 2 | +A number container system that uses binary search to delete and insert values into |
| 3 | +arrays with O(n logn) write times and O(1) read times. |
| 4 | +
|
| 5 | +This container system holds integers at indexes. |
| 6 | +
|
| 7 | +Further explained in this leetcode problem |
| 8 | +> https://leetcode.com/problems/minimum-cost-tree-from-leaf-values |
| 9 | +""" |
| 10 | + |
| 11 | + |
| 12 | +class NumberContainer: |
| 13 | + def __init__(self) -> None: |
| 14 | + # numbermap keys are the number and its values are lists of indexes sorted |
| 15 | + # in ascending order |
| 16 | + self.numbermap: dict[int, list[int]] = {} |
| 17 | + # indexmap keys are an index and it's values are the number at that index |
| 18 | + self.indexmap: dict[int, int] = {} |
| 19 | + |
| 20 | + def binary_search_delete(self, array: list | str | range, item: int) -> list[int]: |
| 21 | + """ |
| 22 | + Removes the item from the sorted array and returns |
| 23 | + the new array. |
| 24 | +
|
| 25 | + >>> NumberContainer().binary_search_delete([1,2,3], 2) |
| 26 | + [1, 3] |
| 27 | + >>> NumberContainer().binary_search_delete([0, 0, 0], 0) |
| 28 | + [0, 0] |
| 29 | + >>> NumberContainer().binary_search_delete([-1, -1, -1], -1) |
| 30 | + [-1, -1] |
| 31 | + >>> NumberContainer().binary_search_delete([-1, 0], 0) |
| 32 | + [-1] |
| 33 | + >>> NumberContainer().binary_search_delete([-1, 0], -1) |
| 34 | + [0] |
| 35 | + >>> NumberContainer().binary_search_delete(range(7), 3) |
| 36 | + [0, 1, 2, 4, 5, 6] |
| 37 | + >>> NumberContainer().binary_search_delete([1.1, 2.2, 3.3], 2.2) |
| 38 | + [1.1, 3.3] |
| 39 | + >>> NumberContainer().binary_search_delete("abcde", "c") |
| 40 | + ['a', 'b', 'd', 'e'] |
| 41 | + >>> NumberContainer().binary_search_delete([0, -1, 2, 4], 0) |
| 42 | + Traceback (most recent call last): |
| 43 | + ... |
| 44 | + ValueError: Either the item is not in the array or the array was unsorted |
| 45 | + >>> NumberContainer().binary_search_delete([2, 0, 4, -1, 11], -1) |
| 46 | + Traceback (most recent call last): |
| 47 | + ... |
| 48 | + ValueError: Either the item is not in the array or the array was unsorted |
| 49 | + >>> NumberContainer().binary_search_delete(125, 1) |
| 50 | + Traceback (most recent call last): |
| 51 | + ... |
| 52 | + TypeError: binary_search_delete() only accepts either a list, range or str |
| 53 | + """ |
| 54 | + if isinstance(array, (range, str)): |
| 55 | + array = list(array) |
| 56 | + elif not isinstance(array, list): |
| 57 | + raise TypeError( |
| 58 | + "binary_search_delete() only accepts either a list, range or str" |
| 59 | + ) |
| 60 | + |
| 61 | + low = 0 |
| 62 | + high = len(array) - 1 |
| 63 | + |
| 64 | + while low <= high: |
| 65 | + mid = (low + high) // 2 |
| 66 | + if array[mid] == item: |
| 67 | + array.pop(mid) |
| 68 | + return array |
| 69 | + elif array[mid] < item: |
| 70 | + low = mid + 1 |
| 71 | + else: |
| 72 | + high = mid - 1 |
| 73 | + raise ValueError( |
| 74 | + "Either the item is not in the array or the array was unsorted" |
| 75 | + ) |
| 76 | + |
| 77 | + def binary_search_insert(self, array: list | str | range, index: int) -> list[int]: |
| 78 | + """ |
| 79 | + Inserts the index into the sorted array |
| 80 | + at the correct position. |
| 81 | +
|
| 82 | + >>> NumberContainer().binary_search_insert([1,2,3], 2) |
| 83 | + [1, 2, 2, 3] |
| 84 | + >>> NumberContainer().binary_search_insert([0,1,3], 2) |
| 85 | + [0, 1, 2, 3] |
| 86 | + >>> NumberContainer().binary_search_insert([-5, -3, 0, 0, 11, 103], 51) |
| 87 | + [-5, -3, 0, 0, 11, 51, 103] |
| 88 | + >>> NumberContainer().binary_search_insert([-5, -3, 0, 0, 11, 100, 103], 101) |
| 89 | + [-5, -3, 0, 0, 11, 100, 101, 103] |
| 90 | + >>> NumberContainer().binary_search_insert(range(10), 4) |
| 91 | + [0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9] |
| 92 | + >>> NumberContainer().binary_search_insert("abd", "c") |
| 93 | + ['a', 'b', 'c', 'd'] |
| 94 | + >>> NumberContainer().binary_search_insert(131, 23) |
| 95 | + Traceback (most recent call last): |
| 96 | + ... |
| 97 | + TypeError: binary_search_insert() only accepts either a list, range or str |
| 98 | + """ |
| 99 | + if isinstance(array, (range, str)): |
| 100 | + array = list(array) |
| 101 | + elif not isinstance(array, list): |
| 102 | + raise TypeError( |
| 103 | + "binary_search_insert() only accepts either a list, range or str" |
| 104 | + ) |
| 105 | + |
| 106 | + low = 0 |
| 107 | + high = len(array) - 1 |
| 108 | + |
| 109 | + while low <= high: |
| 110 | + mid = (low + high) // 2 |
| 111 | + if array[mid] == index: |
| 112 | + # If the item already exists in the array, |
| 113 | + # insert it after the existing item |
| 114 | + array.insert(mid + 1, index) |
| 115 | + return array |
| 116 | + elif array[mid] < index: |
| 117 | + low = mid + 1 |
| 118 | + else: |
| 119 | + high = mid - 1 |
| 120 | + |
| 121 | + # If the item doesn't exist in the array, insert it at the appropriate position |
| 122 | + array.insert(low, index) |
| 123 | + return array |
| 124 | + |
| 125 | + def change(self, index: int, number: int) -> None: |
| 126 | + """ |
| 127 | + Changes (sets) the index as number |
| 128 | +
|
| 129 | + >>> cont = NumberContainer() |
| 130 | + >>> cont.change(0, 10) |
| 131 | + >>> cont.change(0, 20) |
| 132 | + >>> cont.change(-13, 20) |
| 133 | + >>> cont.change(-100030, 20032903290) |
| 134 | + """ |
| 135 | + # Remove previous index |
| 136 | + if index in self.indexmap: |
| 137 | + n = self.indexmap[index] |
| 138 | + if len(self.numbermap[n]) == 1: |
| 139 | + del self.numbermap[n] |
| 140 | + else: |
| 141 | + self.numbermap[n] = self.binary_search_delete(self.numbermap[n], index) |
| 142 | + |
| 143 | + # Set new index |
| 144 | + self.indexmap[index] = number |
| 145 | + |
| 146 | + # Number not seen before or empty so insert number value |
| 147 | + if number not in self.numbermap: |
| 148 | + self.numbermap[number] = [index] |
| 149 | + |
| 150 | + # Here we need to perform a binary search insertion in order to insert |
| 151 | + # The item in the correct place |
| 152 | + else: |
| 153 | + self.numbermap[number] = self.binary_search_insert( |
| 154 | + self.numbermap[number], index |
| 155 | + ) |
| 156 | + |
| 157 | + def find(self, number: int) -> int: |
| 158 | + """ |
| 159 | + Returns the smallest index where the number is. |
| 160 | +
|
| 161 | + >>> cont = NumberContainer() |
| 162 | + >>> cont.find(10) |
| 163 | + -1 |
| 164 | + >>> cont.change(0, 10) |
| 165 | + >>> cont.find(10) |
| 166 | + 0 |
| 167 | + >>> cont.change(0, 20) |
| 168 | + >>> cont.find(10) |
| 169 | + -1 |
| 170 | + >>> cont.find(20) |
| 171 | + 0 |
| 172 | + """ |
| 173 | + # Simply return the 0th index (smallest) of the indexes found (or -1) |
| 174 | + return self.numbermap.get(number, [-1])[0] |
| 175 | + |
| 176 | + |
| 177 | +if __name__ == "__main__": |
| 178 | + import doctest |
| 179 | + |
| 180 | + doctest.testmod() |
0 commit comments