pyochain 0.5.1__py3-none-any.whl → 0.5.32__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyochain might be problematic. Click here for more details.
- pyochain/__init__.py +2 -2
- pyochain/_core/__init__.py +3 -1
- pyochain/_core/_format.py +34 -0
- pyochain/_core/_main.py +65 -44
- pyochain/_core/_protocols.py +2 -7
- pyochain/_dict/__init__.py +1 -2
- pyochain/_dict/_filters.py +38 -53
- pyochain/_dict/_groups.py +7 -8
- pyochain/_dict/_iter.py +52 -9
- pyochain/_dict/_joins.py +11 -9
- pyochain/_dict/_main.py +32 -226
- pyochain/_dict/_maps.py +142 -0
- pyochain/_dict/_nested.py +119 -65
- pyochain/_dict/_process.py +40 -7
- pyochain/_iter/_aggregations.py +1 -0
- pyochain/_iter/_booleans.py +3 -0
- pyochain/_iter/_dicts.py +243 -0
- pyochain/_iter/_eager.py +60 -22
- pyochain/_iter/_filters.py +40 -49
- pyochain/_iter/_joins.py +13 -16
- pyochain/_iter/_lists.py +11 -9
- pyochain/_iter/_main.py +297 -60
- pyochain/_iter/_maps.py +55 -39
- pyochain/_iter/_partitions.py +11 -14
- pyochain/_iter/_process.py +26 -44
- pyochain/_iter/_rolling.py +22 -28
- pyochain/_iter/_tuples.py +119 -14
- {pyochain-0.5.1.dist-info → pyochain-0.5.32.dist-info}/METADATA +8 -42
- pyochain-0.5.32.dist-info/RECORD +32 -0
- pyochain/_dict/_exprs.py +0 -115
- pyochain/_dict/_funcs.py +0 -62
- pyochain/_iter/_constructors.py +0 -155
- pyochain/_iter/_groups.py +0 -264
- pyochain-0.5.1.dist-info/RECORD +0 -33
- {pyochain-0.5.1.dist-info → pyochain-0.5.32.dist-info}/WHEEL +0 -0
pyochain/__init__.py
CHANGED
pyochain/_core/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
from ._format import Peeked, peek, peekn
|
|
1
2
|
from ._main import CommonBase, IterWrapper, MappingWrapper, Pipeable, Wrapper
|
|
2
3
|
from ._protocols import (
|
|
3
|
-
Peeked,
|
|
4
4
|
SizedIterable,
|
|
5
5
|
SupportsAllComparisons,
|
|
6
6
|
SupportsKeysAndGetItem,
|
|
@@ -18,4 +18,6 @@ __all__ = [
|
|
|
18
18
|
"Peeked",
|
|
19
19
|
"SizedIterable",
|
|
20
20
|
"Pipeable",
|
|
21
|
+
"peek",
|
|
22
|
+
"peekn",
|
|
21
23
|
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
2
|
+
from pprint import pformat
|
|
3
|
+
from typing import Any, NamedTuple
|
|
4
|
+
|
|
5
|
+
import cytoolz as cz
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Peeked[T](NamedTuple):
|
|
9
|
+
value: T | tuple[T, ...]
|
|
10
|
+
sequence: Iterator[T]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def peekn[T](data: Iterable[T], n: int) -> Iterator[T]:
|
|
14
|
+
peeked = Peeked(*cz.itertoolz.peekn(n, data))
|
|
15
|
+
print(f"Peeked {n} values: {peeked.value}")
|
|
16
|
+
return peeked.sequence
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def peek[T](data: Iterable[T]) -> Iterator[T]:
|
|
20
|
+
peeked = Peeked(*cz.itertoolz.peek(data))
|
|
21
|
+
print(f"Peeked value: {peeked.value}")
|
|
22
|
+
return peeked.sequence
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def dict_repr(
|
|
26
|
+
v: Mapping[Any, Any],
|
|
27
|
+
max_items: int = 20,
|
|
28
|
+
depth: int = 3,
|
|
29
|
+
width: int = 80,
|
|
30
|
+
compact: bool = True,
|
|
31
|
+
) -> str:
|
|
32
|
+
truncated = dict(list(v.items())[:max_items])
|
|
33
|
+
suffix = "..." if len(v) > max_items else ""
|
|
34
|
+
return pformat(truncated, depth=depth, width=width, compact=compact) + suffix
|
pyochain/_core/_main.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from collections.abc import Callable,
|
|
4
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Concatenate, Self
|
|
6
6
|
|
|
7
|
+
from ._format import dict_repr
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from .._dict import Dict
|
|
9
11
|
from .._iter import Iter, Seq
|
|
@@ -27,12 +29,12 @@ class CommonBase[T](ABC, Pipeable):
|
|
|
27
29
|
The pipe unwrap method must be implemented to allow piping functions that transform the underlying data type, whilst retaining the wrapper.
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
_inner: T
|
|
31
33
|
|
|
32
|
-
__slots__ = ("
|
|
34
|
+
__slots__ = ("_inner",)
|
|
33
35
|
|
|
34
36
|
def __init__(self, data: T) -> None:
|
|
35
|
-
self.
|
|
37
|
+
self._inner = data
|
|
36
38
|
|
|
37
39
|
@abstractmethod
|
|
38
40
|
def apply[**P](
|
|
@@ -53,9 +55,9 @@ class CommonBase[T](ABC, Pipeable):
|
|
|
53
55
|
from pprint import pprint
|
|
54
56
|
|
|
55
57
|
if pretty:
|
|
56
|
-
|
|
58
|
+
self.into(pprint, sort_dicts=False)
|
|
57
59
|
else:
|
|
58
|
-
|
|
60
|
+
self.into(print)
|
|
59
61
|
return self
|
|
60
62
|
|
|
61
63
|
def unwrap(self) -> T:
|
|
@@ -64,7 +66,7 @@ class CommonBase[T](ABC, Pipeable):
|
|
|
64
66
|
|
|
65
67
|
This is a terminal operation.
|
|
66
68
|
"""
|
|
67
|
-
return self.
|
|
69
|
+
return self._inner
|
|
68
70
|
|
|
69
71
|
def into[**P, R](
|
|
70
72
|
self,
|
|
@@ -86,57 +88,73 @@ class CommonBase[T](ABC, Pipeable):
|
|
|
86
88
|
"""
|
|
87
89
|
return func(self.unwrap(), *args, **kwargs)
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
class IterWrapper[T](CommonBase[Iterable[T]]):
|
|
91
|
-
_data: Iterable[T]
|
|
92
|
-
|
|
93
|
-
def apply[**P, R](
|
|
94
|
-
self,
|
|
95
|
-
func: Callable[Concatenate[Iterable[T], P], Iterator[R]],
|
|
96
|
-
*args: P.args,
|
|
97
|
-
**kwargs: P.kwargs,
|
|
98
|
-
) -> Iter[R]:
|
|
91
|
+
def equals_to(self, other: Self | T) -> bool:
|
|
99
92
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
Check if two records are equal based on their data.
|
|
94
|
+
|
|
102
95
|
Args:
|
|
103
|
-
|
|
104
|
-
*args: Positional arguments to pass to the function.
|
|
105
|
-
**kwargs: Keyword arguments to pass to the function.
|
|
96
|
+
other: Another instance or corresponding underlying data to compare against.
|
|
106
97
|
|
|
107
98
|
Example:
|
|
108
99
|
```python
|
|
109
100
|
>>> import pyochain as pc
|
|
110
|
-
>>>
|
|
111
|
-
|
|
112
|
-
>>> pc.
|
|
113
|
-
|
|
101
|
+
>>> d1 = pc.Dict({"a": 1, "b": 2})
|
|
102
|
+
>>> d2 = pc.Dict({"a": 1, "b": 2})
|
|
103
|
+
>>> d3 = pc.Dict({"a": 1, "b": 3})
|
|
104
|
+
>>> d1.equals_to(d2)
|
|
105
|
+
True
|
|
106
|
+
>>> d1.equals_to(d3)
|
|
107
|
+
False
|
|
108
|
+
|
|
109
|
+
```
|
|
114
110
|
"""
|
|
115
|
-
|
|
111
|
+
other_data = other.unwrap() if isinstance(other, self.__class__) else other
|
|
112
|
+
return self.unwrap() == other_data
|
|
116
113
|
|
|
117
|
-
return Iter(self.into(func, *args, **kwargs))
|
|
118
114
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
Collect the elements into a sequence.
|
|
122
|
-
Args:
|
|
123
|
-
factory: A callable that takes an iterable and returns a collection. Defaults to list.
|
|
115
|
+
class IterWrapper[T](CommonBase[Iterable[T]]):
|
|
116
|
+
_inner: Iterable[T]
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
>>> import pyochain as pc
|
|
128
|
-
>>> pc.Iter.from_(range(5)).collect().unwrap()
|
|
129
|
-
[0, 1, 2, 3, 4]
|
|
118
|
+
def __repr__(self) -> str:
|
|
119
|
+
return f"{self.__class__.__name__}({self.unwrap().__repr__()})"
|
|
130
120
|
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
def _eager[**P, U](
|
|
122
|
+
self,
|
|
123
|
+
factory: Callable[Concatenate[Iterable[T], P], Sequence[U]],
|
|
124
|
+
*args: P.args,
|
|
125
|
+
**kwargs: P.kwargs,
|
|
126
|
+
) -> Seq[U]:
|
|
133
127
|
from .._iter import Seq
|
|
134
128
|
|
|
135
|
-
|
|
129
|
+
def _(data: Iterable[T]):
|
|
130
|
+
return Seq(factory(data, *args, **kwargs))
|
|
131
|
+
|
|
132
|
+
return self.into(_)
|
|
133
|
+
|
|
134
|
+
def _lazy[**P, U](
|
|
135
|
+
self,
|
|
136
|
+
factory: Callable[Concatenate[Iterable[T], P], Iterator[U]],
|
|
137
|
+
*args: P.args,
|
|
138
|
+
**kwargs: P.kwargs,
|
|
139
|
+
) -> Iter[U]:
|
|
140
|
+
from .._iter import Iter
|
|
141
|
+
|
|
142
|
+
def _(data: Iterable[T]):
|
|
143
|
+
return Iter(factory(data, *args, **kwargs))
|
|
144
|
+
|
|
145
|
+
return self.into(_)
|
|
136
146
|
|
|
137
147
|
|
|
138
148
|
class MappingWrapper[K, V](CommonBase[dict[K, V]]):
|
|
139
|
-
|
|
149
|
+
_inner: dict[K, V]
|
|
150
|
+
|
|
151
|
+
def __repr__(self) -> str:
|
|
152
|
+
return f"{self.into(dict_repr)}"
|
|
153
|
+
|
|
154
|
+
def _new[KU, VU](self, func: Callable[[dict[K, V]], dict[KU, VU]]) -> Dict[KU, VU]:
|
|
155
|
+
from .._dict import Dict
|
|
156
|
+
|
|
157
|
+
return Dict(func(self.unwrap()))
|
|
140
158
|
|
|
141
159
|
def apply[**P, KU, VU](
|
|
142
160
|
self,
|
|
@@ -147,6 +165,7 @@ class MappingWrapper[K, V](CommonBase[dict[K, V]]):
|
|
|
147
165
|
"""
|
|
148
166
|
Apply a function to the underlying dict and return a Dict of the result.
|
|
149
167
|
Allow to pass user defined functions that transform the dict while retaining the Dict wrapper.
|
|
168
|
+
|
|
150
169
|
Args:
|
|
151
170
|
func: Function to apply to the underlying dict.
|
|
152
171
|
*args: Positional arguments to pass to the function.
|
|
@@ -161,9 +180,11 @@ class MappingWrapper[K, V](CommonBase[dict[K, V]]):
|
|
|
161
180
|
|
|
162
181
|
```
|
|
163
182
|
"""
|
|
164
|
-
from .._dict import Dict
|
|
165
183
|
|
|
166
|
-
|
|
184
|
+
def _(data: dict[K, V]) -> dict[KU, VU]:
|
|
185
|
+
return func(data, *args, **kwargs)
|
|
186
|
+
|
|
187
|
+
return self._new(_)
|
|
167
188
|
|
|
168
189
|
|
|
169
190
|
class Wrapper[T](CommonBase[T]):
|
pyochain/_core/_protocols.py
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
from collections.abc import Iterable,
|
|
2
|
-
from typing import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Peeked[T](NamedTuple):
|
|
6
|
-
value: T | tuple[T, ...]
|
|
7
|
-
sequence: Iterator[T]
|
|
1
|
+
from collections.abc import Iterable, Sized
|
|
2
|
+
from typing import Protocol
|
|
8
3
|
|
|
9
4
|
|
|
10
5
|
class SupportsDunderLT[T](Protocol):
|
pyochain/_dict/__init__.py
CHANGED
pyochain/_dict/_filters.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable, Mapping
|
|
4
4
|
from functools import partial
|
|
5
|
-
from typing import TYPE_CHECKING, Any,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeIs, overload
|
|
6
6
|
|
|
7
7
|
import cytoolz as cz
|
|
8
8
|
|
|
@@ -13,9 +13,15 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class FilterDict[K, V](MappingWrapper[K, V]):
|
|
16
|
-
|
|
16
|
+
@overload
|
|
17
|
+
def filter_keys[U](self, predicate: Callable[[K], TypeIs[U]]) -> Dict[U, V]: ...
|
|
18
|
+
@overload
|
|
19
|
+
def filter_keys(self, predicate: Callable[[K], bool]) -> Dict[K, V]: ...
|
|
20
|
+
def filter_keys[U](
|
|
21
|
+
self, predicate: Callable[[K], bool | TypeIs[U]]
|
|
22
|
+
) -> Dict[K, V] | Dict[U, V]:
|
|
17
23
|
"""
|
|
18
|
-
Return
|
|
24
|
+
Return keys that satisfy predicate.
|
|
19
25
|
|
|
20
26
|
Args:
|
|
21
27
|
predicate: Function to determine if a key should be included.
|
|
@@ -28,11 +34,17 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
28
34
|
|
|
29
35
|
```
|
|
30
36
|
"""
|
|
31
|
-
return self.
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
return self._new(partial(cz.dicttoolz.keyfilter, predicate))
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def filter_values[U](self, predicate: Callable[[V], TypeIs[U]]) -> Dict[K, U]: ...
|
|
41
|
+
@overload
|
|
42
|
+
def filter_values(self, predicate: Callable[[V], bool]) -> Dict[K, V]: ...
|
|
43
|
+
def filter_values[U](
|
|
44
|
+
self, predicate: Callable[[V], bool] | Callable[[V], TypeIs[U]]
|
|
45
|
+
) -> Dict[K, V] | Dict[K, U]:
|
|
34
46
|
"""
|
|
35
|
-
Return
|
|
47
|
+
Return items whose values satisfy predicate.
|
|
36
48
|
|
|
37
49
|
Args:
|
|
38
50
|
predicate: Function to determine if a value should be included.
|
|
@@ -47,12 +59,9 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
47
59
|
|
|
48
60
|
```
|
|
49
61
|
"""
|
|
50
|
-
return self.
|
|
62
|
+
return self._new(partial(cz.dicttoolz.valfilter, predicate))
|
|
51
63
|
|
|
52
|
-
def filter_items(
|
|
53
|
-
self,
|
|
54
|
-
predicate: Callable[[tuple[K, V]], bool],
|
|
55
|
-
) -> Dict[K, V]:
|
|
64
|
+
def filter_items(self, predicate: Callable[[tuple[K, V]], bool]) -> Dict[K, V]:
|
|
56
65
|
"""
|
|
57
66
|
Filter items by predicate applied to (key, value) tuples.
|
|
58
67
|
|
|
@@ -73,12 +82,9 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
73
82
|
|
|
74
83
|
```
|
|
75
84
|
"""
|
|
76
|
-
return self.
|
|
85
|
+
return self._new(partial(cz.dicttoolz.itemfilter, predicate))
|
|
77
86
|
|
|
78
|
-
def filter_kv(
|
|
79
|
-
self,
|
|
80
|
-
predicate: Callable[[K, V], bool],
|
|
81
|
-
) -> Dict[K, V]:
|
|
87
|
+
def filter_kv(self, predicate: Callable[[K, V], bool]) -> Dict[K, V]:
|
|
82
88
|
"""
|
|
83
89
|
Filter items by predicate applied to unpacked (key, value) tuples.
|
|
84
90
|
|
|
@@ -105,13 +111,14 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
105
111
|
|
|
106
112
|
return cz.dicttoolz.itemfilter(_, data)
|
|
107
113
|
|
|
108
|
-
return self.
|
|
114
|
+
return self._new(_filter_kv)
|
|
109
115
|
|
|
110
116
|
def filter_attr[U](self, attr: str, dtype: type[U] = object) -> Dict[K, U]:
|
|
111
117
|
"""
|
|
112
118
|
Filter values that have a given attribute.
|
|
113
119
|
|
|
114
120
|
This does not enforce type checking at runtime for performance considerations.
|
|
121
|
+
|
|
115
122
|
Args:
|
|
116
123
|
attr: Attribute name to check for.
|
|
117
124
|
dtype: Optional expected type of the attribute for type hinting.
|
|
@@ -127,19 +134,19 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
127
134
|
"""
|
|
128
135
|
|
|
129
136
|
def _filter_attr(data: dict[K, V]) -> dict[K, U]:
|
|
130
|
-
def has_attr(x:
|
|
137
|
+
def has_attr(x: object) -> TypeIs[U]:
|
|
131
138
|
return hasattr(x, attr)
|
|
132
139
|
|
|
133
140
|
return cz.dicttoolz.valfilter(has_attr, data)
|
|
134
141
|
|
|
135
|
-
return self.
|
|
142
|
+
return self._new(_filter_attr)
|
|
136
143
|
|
|
137
|
-
def filter_type[R](self,
|
|
144
|
+
def filter_type[R](self, dtype: type[R]) -> Dict[K, R]:
|
|
138
145
|
"""
|
|
139
146
|
Filter values by type.
|
|
140
147
|
|
|
141
148
|
Args:
|
|
142
|
-
|
|
149
|
+
dtype: Type to filter values by.
|
|
143
150
|
Example:
|
|
144
151
|
```python
|
|
145
152
|
>>> import pyochain as pc
|
|
@@ -151,34 +158,12 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
151
158
|
"""
|
|
152
159
|
|
|
153
160
|
def _filter_type(data: dict[K, V]) -> dict[K, R]:
|
|
154
|
-
def _(x:
|
|
155
|
-
return isinstance(x,
|
|
156
|
-
|
|
157
|
-
return cz.dicttoolz.valfilter(_, data)
|
|
158
|
-
|
|
159
|
-
return self.apply(_filter_type)
|
|
160
|
-
|
|
161
|
-
def filter_callable(self) -> Dict[K, Callable[..., Any]]:
|
|
162
|
-
"""
|
|
163
|
-
Filter values that are callable.
|
|
164
|
-
```python
|
|
165
|
-
>>> import pyochain as pc
|
|
166
|
-
>>> def foo():
|
|
167
|
-
... pass
|
|
168
|
-
>>> data = {1: "one", 2: "two", 3: foo, 4: print}
|
|
169
|
-
>>> pc.Dict(data).filter_callable().map_values(lambda x: x.__name__).unwrap()
|
|
170
|
-
{3: 'foo', 4: 'print'}
|
|
171
|
-
|
|
172
|
-
```
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
def _filter_callable(data: dict[K, V]) -> dict[K, Callable[..., Any]]:
|
|
176
|
-
def _(x: V) -> TypeGuard[Callable[..., Any]]:
|
|
177
|
-
return callable(x)
|
|
161
|
+
def _(x: object) -> TypeIs[R]:
|
|
162
|
+
return isinstance(x, dtype)
|
|
178
163
|
|
|
179
164
|
return cz.dicttoolz.valfilter(_, data)
|
|
180
165
|
|
|
181
|
-
return self.
|
|
166
|
+
return self._new(_filter_type)
|
|
182
167
|
|
|
183
168
|
def filter_subclass[U: type[Any], R](
|
|
184
169
|
self: FilterDict[K, U], parent: type[R], keep_parent: bool = True
|
|
@@ -210,7 +195,7 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
210
195
|
"""
|
|
211
196
|
|
|
212
197
|
def _filter_subclass(data: dict[K, U]) -> dict[K, type[R]]:
|
|
213
|
-
def _(x: type[Any]) ->
|
|
198
|
+
def _(x: type[Any]) -> TypeIs[type[R]]:
|
|
214
199
|
if keep_parent:
|
|
215
200
|
return issubclass(x, parent)
|
|
216
201
|
else:
|
|
@@ -218,11 +203,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
218
203
|
|
|
219
204
|
return cz.dicttoolz.valfilter(_, data)
|
|
220
205
|
|
|
221
|
-
return self.
|
|
206
|
+
return self._new(_filter_subclass)
|
|
222
207
|
|
|
223
208
|
def intersect_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
|
|
224
209
|
"""
|
|
225
|
-
|
|
210
|
+
Keep only keys present in self and all others mappings.
|
|
226
211
|
|
|
227
212
|
Args:
|
|
228
213
|
*others: Other mappings to intersect keys with.
|
|
@@ -244,11 +229,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
244
229
|
self_keys.intersection_update(other.keys())
|
|
245
230
|
return {k: data[k] for k in self_keys}
|
|
246
231
|
|
|
247
|
-
return self.
|
|
232
|
+
return self._new(_intersect_keys)
|
|
248
233
|
|
|
249
234
|
def diff_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
|
|
250
235
|
"""
|
|
251
|
-
|
|
236
|
+
Keep only keys present in self but not in others mappings.
|
|
252
237
|
|
|
253
238
|
Args:
|
|
254
239
|
*others: Other mappings to exclude keys from.
|
|
@@ -270,4 +255,4 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
270
255
|
self_keys.difference_update(other.keys())
|
|
271
256
|
return {k: data[k] for k in self_keys}
|
|
272
257
|
|
|
273
|
-
return self.
|
|
258
|
+
return self._new(_diff_keys)
|
pyochain/_dict/_groups.py
CHANGED
|
@@ -34,7 +34,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
|
|
|
34
34
|
|
|
35
35
|
return cz.dicttoolz.valmap(dict, cz.itertoolz.groupby(_, data.items()))
|
|
36
36
|
|
|
37
|
-
return self.
|
|
37
|
+
return self._new(_group_by_value)
|
|
38
38
|
|
|
39
39
|
def group_by_key[G](self, func: Callable[[K], G]) -> Dict[G, dict[K, V]]:
|
|
40
40
|
"""
|
|
@@ -58,7 +58,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
|
|
|
58
58
|
|
|
59
59
|
return cz.dicttoolz.valmap(dict, cz.itertoolz.groupby(_, data.items()))
|
|
60
60
|
|
|
61
|
-
return self.
|
|
61
|
+
return self._new(_group_by_key)
|
|
62
62
|
|
|
63
63
|
def group_by_key_agg[G, R](
|
|
64
64
|
self,
|
|
@@ -72,7 +72,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
|
|
|
72
72
|
key_func: Function to determine the group for each key.
|
|
73
73
|
agg_func: Function to aggregate each sub-dictionary.
|
|
74
74
|
|
|
75
|
-
This avoids materializing intermediate `
|
|
75
|
+
This avoids materializing intermediate `dict` objects if you only need
|
|
76
76
|
an aggregated result for each group.
|
|
77
77
|
```python
|
|
78
78
|
>>> import pyochain as pc
|
|
@@ -118,7 +118,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
|
|
|
118
118
|
groups = cz.itertoolz.groupby(_key_func, data.items())
|
|
119
119
|
return cz.dicttoolz.valmap(_agg_func, groups)
|
|
120
120
|
|
|
121
|
-
return self.
|
|
121
|
+
return self._new(_group_by_key_agg)
|
|
122
122
|
|
|
123
123
|
def group_by_value_agg[G, R](
|
|
124
124
|
self,
|
|
@@ -132,7 +132,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
|
|
|
132
132
|
value_func: Function to determine the group for each value.
|
|
133
133
|
agg_func: Function to aggregate each sub-dictionary.
|
|
134
134
|
|
|
135
|
-
This avoids materializing intermediate `
|
|
135
|
+
This avoids materializing intermediate `dict` objects if you only need
|
|
136
136
|
an aggregated result for each group.
|
|
137
137
|
```python
|
|
138
138
|
>>> import pyochain as pc
|
|
@@ -143,8 +143,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
|
|
|
143
143
|
... agg_func=lambda d: d.iter_keys().count(),
|
|
144
144
|
... ).unwrap()
|
|
145
145
|
{'A': 2, 'B': 1}
|
|
146
|
-
>>>
|
|
147
|
-
>>> # --- Exemple 2: Agrégation plus complexe ---
|
|
146
|
+
>>> # Second example
|
|
148
147
|
>>> sales_data = {
|
|
149
148
|
... "store_1": "Electronics",
|
|
150
149
|
... "store_2": "Groceries",
|
|
@@ -173,4 +172,4 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
|
|
|
173
172
|
groups = cz.itertoolz.groupby(_key_func, data.items())
|
|
174
173
|
return cz.dicttoolz.valmap(_agg_func, groups)
|
|
175
174
|
|
|
176
|
-
return self.
|
|
175
|
+
return self._new(_group_by_value_agg)
|
pyochain/_dict/_iter.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable, Iterable, Mapping
|
|
4
|
-
from typing import TYPE_CHECKING, Concatenate
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Concatenate
|
|
5
5
|
|
|
6
6
|
import cytoolz as cz
|
|
7
7
|
|
|
8
8
|
from .._core import MappingWrapper
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
-
from .._iter import Iter
|
|
11
|
+
from .._iter import Iter, Seq
|
|
12
12
|
from ._main import Dict
|
|
13
13
|
|
|
14
14
|
|
|
@@ -43,15 +43,15 @@ class IterDict[K, V](MappingWrapper[K, V]):
|
|
|
43
43
|
|
|
44
44
|
def _itr(data: Mapping[K, Iterable[U]]) -> dict[K, R]:
|
|
45
45
|
def _(v: Iterable[U]) -> R:
|
|
46
|
-
return func(Iter
|
|
46
|
+
return func(Iter(iter(v)), *args, **kwargs)
|
|
47
47
|
|
|
48
48
|
return cz.dicttoolz.valmap(_, data)
|
|
49
49
|
|
|
50
|
-
return self.
|
|
50
|
+
return self._new(_itr)
|
|
51
51
|
|
|
52
52
|
def iter_keys(self) -> Iter[K]:
|
|
53
53
|
"""
|
|
54
|
-
Return
|
|
54
|
+
Return an Iter of the dict's keys.
|
|
55
55
|
```python
|
|
56
56
|
>>> import pyochain as pc
|
|
57
57
|
>>> pc.Dict({1: 2}).iter_keys().into(list)
|
|
@@ -61,7 +61,10 @@ class IterDict[K, V](MappingWrapper[K, V]):
|
|
|
61
61
|
"""
|
|
62
62
|
from .._iter import Iter
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
def _keys(data: dict[K, V]) -> Iter[K]:
|
|
65
|
+
return Iter(iter(data.keys()))
|
|
66
|
+
|
|
67
|
+
return self.into(_keys)
|
|
65
68
|
|
|
66
69
|
def iter_values(self) -> Iter[V]:
|
|
67
70
|
"""
|
|
@@ -75,11 +78,14 @@ class IterDict[K, V](MappingWrapper[K, V]):
|
|
|
75
78
|
"""
|
|
76
79
|
from .._iter import Iter
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
def _values(data: dict[K, V]) -> Iter[V]:
|
|
82
|
+
return Iter(iter(data.values()))
|
|
83
|
+
|
|
84
|
+
return self.into(_values)
|
|
79
85
|
|
|
80
86
|
def iter_items(self) -> Iter[tuple[K, V]]:
|
|
81
87
|
"""
|
|
82
|
-
Return
|
|
88
|
+
Return an Iter of the dict's items.
|
|
83
89
|
```python
|
|
84
90
|
>>> import pyochain as pc
|
|
85
91
|
>>> pc.Dict({1: 2}).iter_items().into(list)
|
|
@@ -89,4 +95,41 @@ class IterDict[K, V](MappingWrapper[K, V]):
|
|
|
89
95
|
"""
|
|
90
96
|
from .._iter import Iter
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
def _items(data: dict[K, V]) -> Iter[tuple[K, V]]:
|
|
99
|
+
return Iter(iter(data.items()))
|
|
100
|
+
|
|
101
|
+
return self.into(_items)
|
|
102
|
+
|
|
103
|
+
def to_arrays(self) -> Seq[list[Any]]:
|
|
104
|
+
"""
|
|
105
|
+
Convert the nested dictionary into a sequence of arrays.
|
|
106
|
+
|
|
107
|
+
The sequence represents all paths from root to leaves.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
>>> import pyochain as pc
|
|
111
|
+
>>> data = {
|
|
112
|
+
... "a": {"b": 1, "c": 2},
|
|
113
|
+
... "d": {"e": {"f": 3}},
|
|
114
|
+
... }
|
|
115
|
+
>>> pc.Dict(data).to_arrays().unwrap()
|
|
116
|
+
[['a', 'b', 1], ['a', 'c', 2], ['d', 'e', 'f', 3]]
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
"""
|
|
120
|
+
from .._iter import Seq
|
|
121
|
+
|
|
122
|
+
def _to_arrays(d: Mapping[Any, Any]) -> list[list[Any]]:
|
|
123
|
+
"""from dictutils.pivot"""
|
|
124
|
+
match d:
|
|
125
|
+
case Mapping():
|
|
126
|
+
arr: list[Any] = []
|
|
127
|
+
for k, v in d.items():
|
|
128
|
+
for el in _to_arrays(v):
|
|
129
|
+
arr.append([k] + el)
|
|
130
|
+
return arr
|
|
131
|
+
|
|
132
|
+
case _:
|
|
133
|
+
return [[d]]
|
|
134
|
+
|
|
135
|
+
return Seq(self.into(_to_arrays))
|
pyochain/_dict/_joins.py
CHANGED
|
@@ -33,7 +33,7 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
|
|
|
33
33
|
def _inner_join(data: Mapping[K, V]) -> dict[K, tuple[V, W]]:
|
|
34
34
|
return {k: (v, other[k]) for k, v in data.items() if k in other}
|
|
35
35
|
|
|
36
|
-
return self.
|
|
36
|
+
return self._new(_inner_join)
|
|
37
37
|
|
|
38
38
|
def left_join[W](self, other: Mapping[K, W]) -> Dict[K, tuple[V, W | None]]:
|
|
39
39
|
"""
|
|
@@ -56,7 +56,7 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
|
|
|
56
56
|
def _left_join(data: Mapping[K, V]) -> dict[K, tuple[V, W | None]]:
|
|
57
57
|
return {k: (v, other.get(k)) for k, v in data.items()}
|
|
58
58
|
|
|
59
|
-
return self.
|
|
59
|
+
return self._new(_left_join)
|
|
60
60
|
|
|
61
61
|
def diff(self, other: Mapping[K, V]) -> Dict[K, tuple[V | None, V | None]]:
|
|
62
62
|
"""
|
|
@@ -77,9 +77,7 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
|
|
|
77
77
|
```
|
|
78
78
|
"""
|
|
79
79
|
|
|
80
|
-
def _diff(
|
|
81
|
-
data: Mapping[K, V], other: Mapping[K, V]
|
|
82
|
-
) -> dict[K, tuple[V | None, V | None]]:
|
|
80
|
+
def _diff(data: Mapping[K, V]) -> dict[K, tuple[V | None, V | None]]:
|
|
83
81
|
all_keys: set[K] = data.keys() | other.keys()
|
|
84
82
|
diffs: dict[K, tuple[V | None, V | None]] = {}
|
|
85
83
|
for key in all_keys:
|
|
@@ -89,11 +87,11 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
|
|
|
89
87
|
diffs[key] = (self_val, other_val)
|
|
90
88
|
return diffs
|
|
91
89
|
|
|
92
|
-
return self.
|
|
90
|
+
return self._new(_diff)
|
|
93
91
|
|
|
94
92
|
def merge(self, *others: Mapping[K, V]) -> Dict[K, V]:
|
|
95
93
|
"""
|
|
96
|
-
Merge other dicts into this one
|
|
94
|
+
Merge other dicts into this one.
|
|
97
95
|
|
|
98
96
|
Args:
|
|
99
97
|
*others: One or more mappings to merge into the current dictionary.
|
|
@@ -108,7 +106,11 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
|
|
|
108
106
|
|
|
109
107
|
```
|
|
110
108
|
"""
|
|
111
|
-
|
|
109
|
+
|
|
110
|
+
def _merge(data: Mapping[K, V]) -> dict[K, V]:
|
|
111
|
+
return cz.dicttoolz.merge(data, *others)
|
|
112
|
+
|
|
113
|
+
return self._new(_merge)
|
|
112
114
|
|
|
113
115
|
def merge_with(
|
|
114
116
|
self, *others: Mapping[K, V], func: Callable[[Iterable[V]], V]
|
|
@@ -134,4 +136,4 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
|
|
|
134
136
|
def _merge_with(data: Mapping[K, V]) -> dict[K, V]:
|
|
135
137
|
return cz.dicttoolz.merge_with(func, data, *others)
|
|
136
138
|
|
|
137
|
-
return self.
|
|
139
|
+
return self._new(_merge_with)
|