pyochain 0.5.1__py3-none-any.whl → 0.5.31__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 +17 -22
- 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 +31 -24
- pyochain/_iter/_joins.py +13 -16
- pyochain/_iter/_lists.py +11 -9
- pyochain/_iter/_main.py +302 -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.31.dist-info}/METADATA +8 -42
- pyochain-0.5.31.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.31.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
|
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
class FilterDict[K, V](MappingWrapper[K, V]):
|
|
16
16
|
def filter_keys(self, predicate: Callable[[K], bool]) -> Dict[K, V]:
|
|
17
17
|
"""
|
|
18
|
-
Return
|
|
18
|
+
Return keys that satisfy predicate.
|
|
19
19
|
|
|
20
20
|
Args:
|
|
21
21
|
predicate: Function to determine if a key should be included.
|
|
@@ -28,11 +28,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
28
28
|
|
|
29
29
|
```
|
|
30
30
|
"""
|
|
31
|
-
return self.
|
|
31
|
+
return self._new(partial(cz.dicttoolz.keyfilter, predicate))
|
|
32
32
|
|
|
33
33
|
def filter_values(self, predicate: Callable[[V], bool]) -> Dict[K, V]:
|
|
34
34
|
"""
|
|
35
|
-
Return
|
|
35
|
+
Return items whose values satisfy predicate.
|
|
36
36
|
|
|
37
37
|
Args:
|
|
38
38
|
predicate: Function to determine if a value should be included.
|
|
@@ -47,12 +47,9 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
47
47
|
|
|
48
48
|
```
|
|
49
49
|
"""
|
|
50
|
-
return self.
|
|
50
|
+
return self._new(partial(cz.dicttoolz.valfilter, predicate))
|
|
51
51
|
|
|
52
|
-
def filter_items(
|
|
53
|
-
self,
|
|
54
|
-
predicate: Callable[[tuple[K, V]], bool],
|
|
55
|
-
) -> Dict[K, V]:
|
|
52
|
+
def filter_items(self, predicate: Callable[[tuple[K, V]], bool]) -> Dict[K, V]:
|
|
56
53
|
"""
|
|
57
54
|
Filter items by predicate applied to (key, value) tuples.
|
|
58
55
|
|
|
@@ -73,12 +70,9 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
73
70
|
|
|
74
71
|
```
|
|
75
72
|
"""
|
|
76
|
-
return self.
|
|
73
|
+
return self._new(partial(cz.dicttoolz.itemfilter, predicate))
|
|
77
74
|
|
|
78
|
-
def filter_kv(
|
|
79
|
-
self,
|
|
80
|
-
predicate: Callable[[K, V], bool],
|
|
81
|
-
) -> Dict[K, V]:
|
|
75
|
+
def filter_kv(self, predicate: Callable[[K, V], bool]) -> Dict[K, V]:
|
|
82
76
|
"""
|
|
83
77
|
Filter items by predicate applied to unpacked (key, value) tuples.
|
|
84
78
|
|
|
@@ -105,13 +99,14 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
105
99
|
|
|
106
100
|
return cz.dicttoolz.itemfilter(_, data)
|
|
107
101
|
|
|
108
|
-
return self.
|
|
102
|
+
return self._new(_filter_kv)
|
|
109
103
|
|
|
110
104
|
def filter_attr[U](self, attr: str, dtype: type[U] = object) -> Dict[K, U]:
|
|
111
105
|
"""
|
|
112
106
|
Filter values that have a given attribute.
|
|
113
107
|
|
|
114
108
|
This does not enforce type checking at runtime for performance considerations.
|
|
109
|
+
|
|
115
110
|
Args:
|
|
116
111
|
attr: Attribute name to check for.
|
|
117
112
|
dtype: Optional expected type of the attribute for type hinting.
|
|
@@ -132,7 +127,7 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
132
127
|
|
|
133
128
|
return cz.dicttoolz.valfilter(has_attr, data)
|
|
134
129
|
|
|
135
|
-
return self.
|
|
130
|
+
return self._new(_filter_attr)
|
|
136
131
|
|
|
137
132
|
def filter_type[R](self, typ: type[R]) -> Dict[K, R]:
|
|
138
133
|
"""
|
|
@@ -156,7 +151,7 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
156
151
|
|
|
157
152
|
return cz.dicttoolz.valfilter(_, data)
|
|
158
153
|
|
|
159
|
-
return self.
|
|
154
|
+
return self._new(_filter_type)
|
|
160
155
|
|
|
161
156
|
def filter_callable(self) -> Dict[K, Callable[..., Any]]:
|
|
162
157
|
"""
|
|
@@ -178,7 +173,7 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
178
173
|
|
|
179
174
|
return cz.dicttoolz.valfilter(_, data)
|
|
180
175
|
|
|
181
|
-
return self.
|
|
176
|
+
return self._new(_filter_callable)
|
|
182
177
|
|
|
183
178
|
def filter_subclass[U: type[Any], R](
|
|
184
179
|
self: FilterDict[K, U], parent: type[R], keep_parent: bool = True
|
|
@@ -218,11 +213,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
218
213
|
|
|
219
214
|
return cz.dicttoolz.valfilter(_, data)
|
|
220
215
|
|
|
221
|
-
return self.
|
|
216
|
+
return self._new(_filter_subclass)
|
|
222
217
|
|
|
223
218
|
def intersect_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
|
|
224
219
|
"""
|
|
225
|
-
|
|
220
|
+
Keep only keys present in self and all others mappings.
|
|
226
221
|
|
|
227
222
|
Args:
|
|
228
223
|
*others: Other mappings to intersect keys with.
|
|
@@ -244,11 +239,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
244
239
|
self_keys.intersection_update(other.keys())
|
|
245
240
|
return {k: data[k] for k in self_keys}
|
|
246
241
|
|
|
247
|
-
return self.
|
|
242
|
+
return self._new(_intersect_keys)
|
|
248
243
|
|
|
249
244
|
def diff_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
|
|
250
245
|
"""
|
|
251
|
-
|
|
246
|
+
Keep only keys present in self but not in others mappings.
|
|
252
247
|
|
|
253
248
|
Args:
|
|
254
249
|
*others: Other mappings to exclude keys from.
|
|
@@ -270,4 +265,4 @@ class FilterDict[K, V](MappingWrapper[K, V]):
|
|
|
270
265
|
self_keys.difference_update(other.keys())
|
|
271
266
|
return {k: data[k] for k in self_keys}
|
|
272
267
|
|
|
273
|
-
return self.
|
|
268
|
+
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)
|