aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/_list.py
blob: 45122bfd994b326d7bc3a703f31a4e1ff7c56fc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from __future__ import annotations

from collections.abc import Callable
from typing import Generic, Iterable, Self, TypeAlias, TypeVar
from ._iter import CruIterable

_T = TypeVar("_T")


class CruListEdit(CruIterable.Wrapper[_T]):
    def done(self) -> CruList[_T]:
        l: CruList[_T] = self.my_attr["list"]
        l.reset(self)
        return l


class CruList(list[_T]):
    def reset(self, new_values: Iterable[_T]):
        if self is new_values:
            new_values = list(new_values)
        self.clear()
        self.extend(new_values)
        return self

    def as_iterable_wrapper(self) -> CruIterable.Wrapper[_T]:
        return CruIterable.Wrapper(self)

    def as_edit_iterable_wrapper(self) -> CruListEdit[_T]:
        return CruListEdit(self, attr={"list": self})

    @staticmethod
    def make(maybe_list: Iterable[_T] | _T | None) -> CruList[_T]:
        if maybe_list is None:
            return CruList()
        if isinstance(maybe_list, Iterable):
            return CruList(maybe_list)
        return CruList([maybe_list])


_K = TypeVar("_K")

_KeyGetter: TypeAlias = Callable[[_T], _K]


class CruUniqueKeyList(Generic[_T, _K]):
    def __init__(
        self,
        key_getter: _KeyGetter[_T, _K],
        *,
        before_add: Callable[[_T], _T] | None = None,
    ):
        super().__init__()
        self._key_getter = key_getter
        self._before_add = before_add
        self._list: CruList[_T] = CruList()

    @property
    def key_getter(self) -> _KeyGetter[_T, _K]:
        return self._key_getter

    @property
    def internal_list(self) -> CruList[_T]:
        return self._list

    def validate_self(self):
        keys = self._list.transform(self._key_getter)
        if len(keys) != len(set(keys)):
            raise ValueError("Duplicate keys!")

    # TODO: Continue here!
    def get_or(self, key: K, fallback: Any = CRU_NOT_FOUND) -> _V | Any:
        r = self._l.find_if(lambda i: k == self._key_getter(i))
        return r if r is not CRU_NOT_FOUND else fallback

    def get(self, k: K) -> _V:
        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._key_getter(i) in k)

    def try_remove(self, k: K) -> bool:
        i = self._l.find_index_if(lambda v: k == self._key_getter(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: _V, /, replace: bool = False) -> None:
        if self.has_key(self._key_getter(v)):
            if replace:
                self.remove(self._key_getter(v))
            else:
                raise ValueError(f"Key {self._key_getter(v)} already exists!")
        if self._before_add is not None:
            v = self._before_add(v)
        self._l.append(v)

    def set(self, v: _V) -> None:
        self.add(v, True)

    def extend(self, l: Iterable[_V], /, replace: bool = False) -> None:
        if not replace and self.has_any_key([self._key_getter(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._key_getter(i) for i in l]
        self._l.remove_all_if(lambda i: self._key_getter(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)