|
|
@@ -3,12 +3,18 @@ Subclasses of list, set, and dict with special behaviors.
|
|
3
|
3
|
"""
|
|
4
|
4
|
|
|
5
|
5
|
from abc import ABCMeta, abstractmethod
|
|
6
|
|
-from typing import Generic, TypeVar
|
|
|
6
|
+from typing import Callable, Generic, TypeVar, Optional
|
|
7
|
7
|
|
|
8
|
8
|
# Abstract collections
|
|
9
|
9
|
|
|
|
10
|
+# Dictionary key
|
|
10
|
11
|
K = TypeVar('K')
|
|
|
12
|
+# Collection value
|
|
11
|
13
|
V = TypeVar('V')
|
|
|
14
|
+# Element age
|
|
|
15
|
+A = TypeVar('A')
|
|
|
16
|
+# Age delta
|
|
|
17
|
+D = TypeVar('D')
|
|
12
|
18
|
|
|
13
|
19
|
class AbstractMutableList(list[V], Generic[V], metaclass=ABCMeta):
|
|
14
|
20
|
"""
|
|
|
@@ -232,7 +238,7 @@ class AbstractMutableDict(dict[K, V], Generic[K, V], metaclass=ABCMeta):
|
|
232
|
238
|
|
|
233
|
239
|
# Collections with limited number of elements
|
|
234
|
240
|
|
|
235
|
|
-class SizeBoundList(AbstractMutableList[V], Generic[V]):
|
|
|
241
|
+class SizeBoundList(AbstractMutableList[V], Generic[V, A]):
|
|
236
|
242
|
"""
|
|
237
|
243
|
Subclass of `list` that enforces a maximum number of elements.
|
|
238
|
244
|
|
|
|
@@ -253,7 +259,7 @@ class SizeBoundList(AbstractMutableList[V], Generic[V]):
|
|
253
|
259
|
"""
|
|
254
|
260
|
def __init__(self,
|
|
255
|
261
|
max_element_count: int,
|
|
256
|
|
- element_age,
|
|
|
262
|
+ element_age: Callable[[int, V], A],
|
|
257
|
263
|
*args, **kwargs):
|
|
258
|
264
|
super().__init__(*args, **kwargs)
|
|
259
|
265
|
self.element_age = element_age
|
|
|
@@ -290,7 +296,7 @@ class SizeBoundList(AbstractMutableList[V], Generic[V]):
|
|
290
|
296
|
def copy(self):
|
|
291
|
297
|
return SizeBoundList(self.max_element_count, self.element_age, super())
|
|
292
|
298
|
|
|
293
|
|
-class SizeBoundSet(AbstractMutableSet[V], Generic[V]):
|
|
|
299
|
+class SizeBoundSet(AbstractMutableSet[V], Generic[V, A]):
|
|
294
|
300
|
"""
|
|
295
|
301
|
Subclass of `set` that enforces a maximum number of elements.
|
|
296
|
302
|
|
|
|
@@ -311,7 +317,7 @@ class SizeBoundSet(AbstractMutableSet[V], Generic[V]):
|
|
311
|
317
|
"""
|
|
312
|
318
|
def __init__(self,
|
|
313
|
319
|
max_element_count: int,
|
|
314
|
|
- element_age,
|
|
|
320
|
+ element_age: Callable[[int, V], A],
|
|
315
|
321
|
*args, **kwargs):
|
|
316
|
322
|
super().__init__(*args, **kwargs)
|
|
317
|
323
|
self.element_age = element_age
|
|
|
@@ -347,7 +353,7 @@ class SizeBoundSet(AbstractMutableSet[V], Generic[V]):
|
|
347
|
353
|
def copy(self):
|
|
348
|
354
|
return SizeBoundSet(self.max_element_count, self.element_age, super())
|
|
349
|
355
|
|
|
350
|
|
-class SizeBoundDict(AbstractMutableDict[K, V], Generic[K, V]):
|
|
|
356
|
+class SizeBoundDict(AbstractMutableDict[K, V], Generic[K, V, A]):
|
|
351
|
357
|
"""
|
|
352
|
358
|
Subclass of `dict` that enforces a maximum number of elements.
|
|
353
|
359
|
|
|
|
@@ -368,7 +374,7 @@ class SizeBoundDict(AbstractMutableDict[K, V], Generic[K, V]):
|
|
368
|
374
|
"""
|
|
369
|
375
|
def __init__(self,
|
|
370
|
376
|
max_element_count: int,
|
|
371
|
|
- element_age,
|
|
|
377
|
+ element_age: Callable[[K, V], A],
|
|
372
|
378
|
*args, **kwargs):
|
|
373
|
379
|
super().__init__(*args, **kwargs)
|
|
374
|
380
|
self.element_age = element_age
|
|
|
@@ -406,7 +412,7 @@ class SizeBoundDict(AbstractMutableDict[K, V], Generic[K, V]):
|
|
406
|
412
|
|
|
407
|
413
|
# Collections with limited age of elements
|
|
408
|
414
|
|
|
409
|
|
-class AgeBoundList(AbstractMutableList[V], Generic[V]):
|
|
|
415
|
+class AgeBoundList(AbstractMutableList[V], Generic[V, A, D]):
|
|
410
|
416
|
"""
|
|
411
|
417
|
Subclass of `list` that enforces a maximum "age" of elements.
|
|
412
|
418
|
|
|
|
@@ -425,7 +431,7 @@ class AgeBoundList(AbstractMutableList[V], Generic[V]):
|
|
425
|
431
|
however elements will only be discarded following the next mutating
|
|
426
|
432
|
operation. Call `self.purge_old_elements()` to force resizing.
|
|
427
|
433
|
"""
|
|
428
|
|
- def __init__(self, max_age, element_age, *args, **kwargs):
|
|
|
434
|
+ def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
|
|
429
|
435
|
super().__init__(*args, **kwargs)
|
|
430
|
436
|
self.max_age = max_age
|
|
431
|
437
|
self.element_age = element_age
|
|
|
@@ -445,17 +451,17 @@ class AgeBoundList(AbstractMutableList[V], Generic[V]):
|
|
445
|
451
|
if self.is_culling or len(self) <= 1:
|
|
446
|
452
|
return
|
|
447
|
453
|
self.is_culling = True
|
|
448
|
|
- min_age = None
|
|
449
|
|
- max_age = None
|
|
450
|
|
- ages = {}
|
|
|
454
|
+ min_age: Optional[A] = None
|
|
|
455
|
+ max_age: Optional[A] = None
|
|
|
456
|
+ ages: dict[int, A] = {}
|
|
451
|
457
|
for i, elem in enumerate(self):
|
|
452
|
|
- age = self.element_age(i, elem)
|
|
|
458
|
+ age: A = self.element_age(i, elem)
|
|
453
|
459
|
ages[i] = age
|
|
454
|
460
|
if min_age is None or age < min_age:
|
|
455
|
461
|
min_age = age
|
|
456
|
462
|
if max_age is None or age > max_age:
|
|
457
|
463
|
max_age = age
|
|
458
|
|
- cutoff = max_age - self.max_age
|
|
|
464
|
+ cutoff: A = max_age - self.max_age
|
|
459
|
465
|
if min_age >= cutoff:
|
|
460
|
466
|
self.is_culling = False
|
|
461
|
467
|
return
|
|
|
@@ -467,7 +473,7 @@ class AgeBoundList(AbstractMutableList[V], Generic[V]):
|
|
467
|
473
|
def copy(self):
|
|
468
|
474
|
return AgeBoundList(self.max_age, self.element_age, super())
|
|
469
|
475
|
|
|
470
|
|
-class AgeBoundSet(AbstractMutableSet[V], Generic[V]):
|
|
|
476
|
+class AgeBoundSet(AbstractMutableSet[V], Generic[V, A, D]):
|
|
471
|
477
|
"""
|
|
472
|
478
|
Subclass of `set` that enforces a maximum "age" of elements.
|
|
473
|
479
|
|
|
|
@@ -486,7 +492,7 @@ class AgeBoundSet(AbstractMutableSet[V], Generic[V]):
|
|
486
|
492
|
however elements will only be discarded following the next mutating
|
|
487
|
493
|
operation. Call `self.purge_old_elements()` to force resizing.
|
|
488
|
494
|
"""
|
|
489
|
|
- def __init__(self, max_age, element_age, *args, **kwargs):
|
|
|
495
|
+ def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
|
|
490
|
496
|
super().__init__(*args, **kwargs)
|
|
491
|
497
|
self.max_age = max_age
|
|
492
|
498
|
self.element_age = element_age
|
|
|
@@ -528,7 +534,7 @@ class AgeBoundSet(AbstractMutableSet[V], Generic[V]):
|
|
528
|
534
|
def copy(self):
|
|
529
|
535
|
return AgeBoundSet(self.max_age, self.element_age, super())
|
|
530
|
536
|
|
|
531
|
|
-class AgeBoundDict(AbstractMutableDict[K, V], Generic[K, V]):
|
|
|
537
|
+class AgeBoundDict(AbstractMutableDict[K, V], Generic[K, V, A, D]):
|
|
532
|
538
|
"""
|
|
533
|
539
|
Subclass of `dict` that enforces a maximum "age" of elements.
|
|
534
|
540
|
|
|
|
@@ -547,7 +553,7 @@ class AgeBoundDict(AbstractMutableDict[K, V], Generic[K, V]):
|
|
547
|
553
|
however elements will only be discarded following the next mutating
|
|
548
|
554
|
operation. Call `self.purge_old_elements()` to force resizing.
|
|
549
|
555
|
"""
|
|
550
|
|
- def __init__(self, max_age, element_age, *args, **kwargs):
|
|
|
556
|
+ def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
|
|
551
|
557
|
super().__init__(*args, **kwargs)
|
|
552
|
558
|
self.max_age = max_age
|
|
553
|
559
|
self.element_age = element_age
|