aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/util/_list.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cru-py/cru/util/_list.py')
-rw-r--r--tools/cru-py/cru/util/_list.py272
1 files changed, 225 insertions, 47 deletions
diff --git a/tools/cru-py/cru/util/_list.py b/tools/cru-py/cru/util/_list.py
index 3b4cb28..92cd88c 100644
--- a/tools/cru-py/cru/util/_list.py
+++ b/tools/cru-py/cru/util/_list.py
@@ -1,17 +1,32 @@
from collections.abc import Iterable, Callable
from typing import TypeVar, ParamSpec, Any, Generic
+from ._const import CRU_NOT_FOUND
+
T = TypeVar("T")
R = TypeVar("R")
P = ParamSpec("P")
CanBeList = T | Iterable[T] | None
+ElementOperation = Callable[[T], Any]
+ElementPredicate = Callable[[T], bool]
+ElementTransformer = Callable[[Any], Any]
+OptionalElementOperation = Callable[[T], Any] | None
+OptionalElementTransformer = Callable[[Any], Any] | None
class ListOperations:
@staticmethod
- def foreach(l: Iterable[T], *f: Callable[[T], None] | None) -> None:
+ def sub_by_indices(l: Iterable[T], *index: int) -> list[T]:
+ return [v for i, v in enumerate(l) if i in index]
+
+ @staticmethod
+ def complement_indices(length: int, *index: int) -> list[int]:
+ return [i for i in range(length) if i not in index]
+
+ @staticmethod
+ def foreach(l: Iterable[T], *f: OptionalElementOperation[T]) -> None:
if len(f) == 0: return
for v in l:
for f_ in f:
@@ -19,88 +34,131 @@ class ListOperations:
f_(v)
@staticmethod
- def transform(l: Iterable[T], *f: Callable | None) -> list[R]:
- r = []
+ def all(l: Iterable[T], p: ElementPredicate[T]) -> bool:
for v in l:
- for f_ in f:
- if f_ is not None:
- v = f_(v)
- r.append(v)
- return r
+ if not p(v): return False
+ return True
@staticmethod
- def transform_if(l: Iterable[T], f: Callable[[T], R] | None, p: Callable[[T], bool] | None) -> list[R]:
- return [(f(v) if f else v) for v in l if (p(v) if p else True)]
+ def all_is_instance(l: Iterable[T], *t: type) -> bool:
+ return all(type(v) in t for v in l)
@staticmethod
- def all(l: Iterable[T], *p: Callable[[T], bool] | None) -> bool:
- if len(p) == 0 or all(v is None for v in p):
- raise ValueError("At least one predicate is required!")
+ def any(l: Iterable[T], p: ElementPredicate[T]) -> bool:
for v in l:
- for p_ in p:
- if p_ is not None and not p_(v): return False
- return True
+ if p(v): return True
+ return False
@staticmethod
- def all_is_instance(l: Iterable[T], *t: type) -> bool:
- return all(type(v) in t for v in l)
+ def find_all_if(l: Iterable[T], p: ElementPredicate[T]) -> list[T]:
+ return [v for v in l if p(v)]
+
+ @staticmethod
+ def find_if(l: Iterable[T], p: ElementPredicate[T]) -> T | CRU_NOT_FOUND:
+ r = ListOperations.find_all_if(l, p)
+ return r[0] if len(r) > 0 else CRU_NOT_FOUND
+
+ @staticmethod
+ def find_all_indices_if(l: Iterable[T], p: ElementPredicate[T]) -> list[int]:
+ return [i for i, v in enumerate(l) if p(v)]
@staticmethod
- def any(l: Iterable[T], *p: Callable[[T], bool] | None) -> bool:
- if len(p) == 0 or all(v is None for v in p):
- raise ValueError("At least one predicate is required!")
+ def find_index_if(l: Iterable[T], p: ElementPredicate[T]) -> int | CRU_NOT_FOUND:
+ r = ListOperations.find_all_indices_if(l, p)
+ return r[0] if len(r) > 0 else CRU_NOT_FOUND
+
+ @staticmethod
+ def transform(l: Iterable[T], *f: OptionalElementTransformer) -> list[Any]:
+ r = []
for v in l:
- for p_ in p:
- if p_ is not None and p_(v): return True
- return False
+ for f_ in f:
+ if f_ is not None:
+ v = f_(v)
+ r.append(v)
+ return r
@staticmethod
- def make(v: CanBeList[T], /, none_to_empty_list: bool = True) -> list[T]:
- if v is None and none_to_empty_list: return []
- return v if isinstance(v, Iterable) else [v]
+ def transform_if(l: Iterable[T], f: OptionalElementTransformer, p: ElementPredicate[T]) -> list[Any]:
+ return [(f(v) if f else v) for v in l if p(v)]
@staticmethod
- def remove_all_if(l: Iterable[T], *p: Callable[[], bool] | None) -> list[T]:
- def stay(v):
- for p_ in p:
- if p_ is not None and p_(): return False
- return True
+ def remove_by_indices(l: Iterable[T], *index: int | None) -> list[T]:
+ return [v for i, v in enumerate(l) if i not in index]
- return [v for v in l if stay(v)]
+ @staticmethod
+ def remove_if(l: Iterable[T], p: ElementPredicate[T]) -> list[T]:
+ i = ListOperations.find_index_if(l, p)
+ return ListOperations.remove_by_indices(l, i)
+
+ @staticmethod
+ def remove_all_if(l: Iterable[T], p: ElementPredicate[T]) -> list[T]:
+ return [v for v in l if not p(v)]
@staticmethod
- def remove_all_value(l: Iterable[T], *r) -> list[T]:
+ def remove_all_value(l: Iterable[T], *r: Any) -> list[T]:
return [v for v in l if v not in r]
@staticmethod
def replace_all_value(l: Iterable[T], old_value: Any, new_value: R) -> list[T | R]:
return [new_value if v == old_value else v for v in l]
+ @staticmethod
+ def make(v: CanBeList[T], /, none_to_empty_list: bool = True) -> list[T]:
+ if v is None and none_to_empty_list: return []
+ return list(v) if isinstance(v, Iterable) else [v]
+
class CruList(list, Generic[T]):
+ @property
+ def is_empty(self) -> bool:
+ return len(self) == 0
- def foreach(self, *f: Callable[[T], None] | None) -> None:
- ListOperations.foreach(self, *f)
+ def sub_by_indices(self, *index: int) -> "CruList"[T]:
+ return CruList(ListOperations.sub_by_indices(self, *index))
- def transform(self, *f: Callable[[T], R] | None) -> "CruList"[R]:
- return CruList(ListOperations.transform(self, *f))
+ def complement_indices(self, *index: int) -> list[int]:
+ return ListOperations.complement_indices(len(self), *index)
- def transform_if(self, f: Callable[[T], R] | None, p: Callable[[T], bool] | None) -> "CruList"[R]:
- return CruList(ListOperations.transform_if(self, f, p))
+ def foreach(self, *f: OptionalElementOperation[T]) -> None:
+ ListOperations.foreach(self, *f)
- def all(self, *p: Callable[[T], bool] | None) -> bool:
- return ListOperations.all(self, *p)
+ def all(self, p: ElementPredicate[T]) -> bool:
+ return ListOperations.all(self, p)
def all_is_instance(self, *t: type) -> bool:
return ListOperations.all_is_instance(self, *t)
- def any(self, *p: Callable[[T], bool] | None) -> bool:
- return ListOperations.any(self, *p)
+ def any(self, p: ElementPredicate[T]) -> bool:
+ return ListOperations.any(self, p)
- def remove_all_if(self, *p: Callable[[]]) -> "CruList"[T]:
- return CruList(ListOperations.remove_all_if(self, *p))
+ def find_all_if(self, p: ElementPredicate[T]) -> "CruList"[T]:
+ return CruList(ListOperations.find_all_if(self, p))
- def remove_all_value(self, *r) -> "CruList"[T]:
+ def find_if(self, p: ElementPredicate[T]) -> T | CRU_NOT_FOUND:
+ return ListOperations.find_if(self, p)
+
+ def find_all_indices_if(self, p: ElementPredicate[T]) -> "CruList"[int]:
+ return CruList(ListOperations.find_all_indices_if(self, p))
+
+ def find_index_if(self, p: ElementPredicate[T]) -> int | CRU_NOT_FOUND:
+ return ListOperations.find_index_if(self, p)
+
+ def transform(self, *f: OptionalElementTransformer) -> "CruList"[Any]:
+ return CruList(ListOperations.transform(self, *f))
+
+ def transform_if(self, f: OptionalElementTransformer, p: ElementPredicate[T]) -> "CruList"[Any]:
+ return CruList(ListOperations.transform_if(self, f, p))
+
+ def remove_by_indices(self, *index: int) -> "CruList"[T]:
+ return CruList(ListOperations.remove_by_indices(self, *index))
+
+ def remove_if(self, p: ElementPredicate[T]) -> "CruList"[T]:
+ return CruList(ListOperations.remove_if(self, p))
+
+ def remove_all_if(self, p: ElementPredicate[T]) -> "CruList"[T]:
+ return CruList(ListOperations.remove_all_if(self, p))
+
+ def remove_all_value(self, *r: Any) -> "CruList"[T]:
return CruList(ListOperations.remove_all_value(self, *r))
def replace_all_value(self, old_value: Any, new_value: R) -> "CruList"[T | R]:
@@ -109,3 +167,123 @@ class CruList(list, Generic[T]):
@staticmethod
def make(l: CanBeList[T]) -> "CruList"[T]:
return CruList(ListOperations.make(l))
+
+
+class CruInplaceList(CruList, Generic[T]):
+
+ def clear(self) -> "CruInplaceList[T]":
+ self.clear()
+ return self
+
+ def extend(self, *l: Iterable[T]) -> "CruInplaceList[T]":
+ self.extend(l)
+ return self
+
+ def reset(self, *l: Iterable[T]) -> "CruInplaceList[T]":
+ self.clear()
+ self.extend(l)
+ return self
+
+ def transform(self, *f: OptionalElementTransformer) -> "CruInplaceList"[Any]:
+ return self.reset(super().transform(*f))
+
+ def transform_if(self, f: OptionalElementTransformer, p: ElementPredicate[T]) -> "CruInplaceList"[Any]:
+ return self.reset(super().transform_if(f, p))
+
+ def remove_by_indices(self, *index: int) -> "CruInplaceList"[T]:
+ return self.reset(super().remove_by_indices(*index))
+
+ def remove_all_if(self, p: ElementPredicate[T]) -> "CruInplaceList"[T]:
+ return self.reset(super().remove_all_if(p))
+
+ def remove_all_value(self, *r: Any) -> "CruInplaceList"[T]:
+ return self.reset(super().remove_all_value(*r))
+
+ def replace_all_value(self, old_value: Any, new_value: R) -> "CruInplaceList"[T | R]:
+ return self.reset(super().replace_all_value(old_value, new_value))
+
+ @staticmethod
+ def make(l: CanBeList[T]) -> "CruInplaceList"[T]:
+ return CruInplaceList(ListOperations.make(l))
+
+
+K = TypeVar("K")
+
+
+class CruUniqueKeyInplaceList(Generic[T, K]):
+ KeyGetter = Callable[[T], K]
+
+ def __init__(self, get_key: KeyGetter, *, before_add: Callable[[T], T] | None = None):
+ super().__init__()
+ self._get_key = get_key
+ self._before_add = before_add
+ self._l: CruInplaceList[T] = CruInplaceList()
+
+ @property
+ def object_key_getter(self) -> KeyGetter:
+ return self._get_key
+
+ @property
+ def internal_list(self) -> CruInplaceList[T]:
+ return self._l
+
+ def validate_self(self):
+ keys = self._l.transform(self._get_key)
+ if len(keys) != len(set(keys)):
+ raise ValueError("Duplicate keys!")
+
+ def get_or(self, k: K, fallback: Any = CRU_NOT_FOUND) -> T | Any:
+ r = self._l.find_if(lambda i: k == self._get_key(i))
+ return r if r is not CRU_NOT_FOUND else fallback
+
+ def get(self, k: K) -> T:
+ v = self.get_or(k, CRU_NOT_FOUND)
+ if v is CRU_NOT_FOUND:
+ raise KeyError(f"Key not found!")
+ return v
+
+ def has_key(self, k: K) -> bool:
+ return self.get_or(k, CRU_NOT_FOUND) is not CRU_NOT_FOUND
+
+ def has_any_key(self, *k: K) -> bool:
+ return self._l.any(lambda i: self._get_key(i) in k)
+
+ def try_remove(self, k: K) -> bool:
+ i = self._l.find_index_if(lambda v: k == self._get_key(v))
+ if i is CRU_NOT_FOUND: return False
+ self._l.remove_by_indices(i)
+ return True
+
+ def remove(self, k: K, allow_absense: bool = False) -> None:
+ if not self.try_remove(k) and not allow_absense:
+ raise KeyError(f"Key {k} not found!")
+
+ def add(self, v: T, /, replace: bool = False) -> None:
+ if self.has_key(self._get_key(v)):
+ if replace:
+ self.remove(self._get_key(v))
+ else:
+ raise ValueError(f"Key {self._get_key(v)} already exists!")
+ if self._before_add is not None:
+ v = self._before_add(v)
+ self._l.append(v)
+
+ def set(self, v: T) -> None:
+ self.add(v, True)
+
+ def extend(self, l: Iterable[T], /, replace: bool = False) -> None:
+ if not replace and self.has_any_key([self._get_key(i) for i in l]):
+ raise ValueError("Keys already exists!")
+ if self._before_add is not None:
+ l = [self._before_add(i) for i in l]
+ keys = [self._get_key(i) for i in l]
+ self._l.remove_all_if(lambda i: self._get_key(i) in keys).extend(l)
+
+ def clear(self) -> None:
+ self._l.clear()
+
+ def __iter__(self):
+ return iter(self._l)
+
+ def __len__(self):
+ return len(self._l)