|
31 | 31 | overload,
|
32 | 32 | )
|
33 | 33 |
|
| 34 | +from sortedcontainers import SortedList |
| 35 | + |
34 | 36 | from hypothesis.errors import HypothesisWarning
|
35 | 37 |
|
36 | 38 | ARRAY_CODES = ["B", "H", "I", "L", "Q", "O"]
|
@@ -199,46 +201,71 @@ class LazySequenceCopy:
|
199 | 201 | in O(1) time. The full list API is not supported yet but there's no reason
|
200 | 202 | in principle it couldn't be."""
|
201 | 203 |
|
202 |
| - __mask: Optional[Dict[int, int]] |
203 |
| - |
204 | 204 | def __init__(self, values: Sequence[int]):
|
205 | 205 | self.__values = values
|
206 | 206 | self.__len = len(values)
|
207 |
| - self.__mask = None |
| 207 | + self.__mask: Optional[Dict[int, int]] = None |
| 208 | + self.__popped_indices: Optional[SortedList] = None |
208 | 209 |
|
209 | 210 | def __len__(self) -> int:
|
210 |
| - return self.__len |
| 211 | + if self.__popped_indices is None: |
| 212 | + return self.__len |
| 213 | + return self.__len - len(self.__popped_indices) |
211 | 214 |
|
212 |
| - def pop(self) -> int: |
| 215 | + def pop(self, i: int = -1) -> int: |
213 | 216 | if len(self) == 0:
|
214 | 217 | raise IndexError("Cannot pop from empty list")
|
215 |
| - result = self[-1] |
216 |
| - self.__len -= 1 |
| 218 | + i = self.__underlying_index(i) |
| 219 | + |
| 220 | + v = None |
217 | 221 | if self.__mask is not None:
|
218 |
| - self.__mask.pop(self.__len, None) |
219 |
| - return result |
| 222 | + v = self.__mask.pop(i, None) |
| 223 | + if v is None: |
| 224 | + v = self.__values[i] |
| 225 | + |
| 226 | + if self.__popped_indices is None: |
| 227 | + self.__popped_indices = SortedList() |
| 228 | + self.__popped_indices.add(i) |
| 229 | + return v |
220 | 230 |
|
221 | 231 | def __getitem__(self, i: int) -> int:
|
222 |
| - i = self.__check_index(i) |
| 232 | + i = self.__underlying_index(i) |
| 233 | + |
223 | 234 | default = self.__values[i]
|
224 | 235 | if self.__mask is None:
|
225 | 236 | return default
|
226 | 237 | else:
|
227 | 238 | return self.__mask.get(i, default)
|
228 | 239 |
|
229 | 240 | def __setitem__(self, i: int, v: int) -> None:
|
230 |
| - i = self.__check_index(i) |
| 241 | + i = self.__underlying_index(i) |
231 | 242 | if self.__mask is None:
|
232 | 243 | self.__mask = {}
|
233 | 244 | self.__mask[i] = v
|
234 | 245 |
|
235 |
| - def __check_index(self, i: int) -> int: |
| 246 | + def __underlying_index(self, i: int) -> int: |
236 | 247 | n = len(self)
|
237 | 248 | if i < -n or i >= n:
|
238 | 249 | raise IndexError(f"Index {i} out of range [0, {n})")
|
239 | 250 | if i < 0:
|
240 | 251 | i += n
|
241 | 252 | assert 0 <= i < n
|
| 253 | + |
| 254 | + if self.__popped_indices is not None: |
| 255 | + # given an index i in the popped representation of the list, compute |
| 256 | + # its corresponding index in the underlying list. given |
| 257 | + # l = [1, 4, 2, 10, 188] |
| 258 | + # l.pop(3) |
| 259 | + # l.pop(1) |
| 260 | + # assert l == [1, 2, 188] |
| 261 | + # |
| 262 | + # we want l[i] == self.__values[f(i)], where f is this function. |
| 263 | + assert len(self.__popped_indices) <= len(self.__values) |
| 264 | + |
| 265 | + for idx in self.__popped_indices: |
| 266 | + if idx > i: |
| 267 | + break |
| 268 | + i += 1 |
242 | 269 | return i
|
243 | 270 |
|
244 | 271 |
|
@@ -345,14 +372,6 @@ def find_integer(f: Callable[[int], bool]) -> int:
|
345 | 372 | return lo
|
346 | 373 |
|
347 | 374 |
|
348 |
| -def pop_random(random: Random, seq: LazySequenceCopy) -> int: |
349 |
| - """Remove and return a random element of seq. This runs in O(1) but leaves |
350 |
| - the sequence in an arbitrary order.""" |
351 |
| - i = random.randrange(0, len(seq)) |
352 |
| - swap(seq, i, len(seq) - 1) |
353 |
| - return seq.pop() |
354 |
| - |
355 |
| - |
356 | 375 | class NotFound(Exception):
|
357 | 376 | pass
|
358 | 377 |
|
|
0 commit comments