bigarraylist 0.2.0__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.
@@ -0,0 +1,212 @@
1
+ """排序算子 mixin:sort / is_sorted / 外部排序接入。
2
+
3
+ 核心改进(对比重构前):
4
+ - 不再内联 _external_sort 实现,改为调用 algorithms.external_sort.external_sort
5
+ - 通过 self._get_config() 读 BigArrayConfig 的阈值,所有阈值可配
6
+ - 默认走 streaming 模式(内存峰值 O(块数) 而非 O(数据总量))
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import bisect as _bisect_mod
12
+ import heapq
13
+ from collections.abc import Callable, Generator, Iterator
14
+ from concurrent.futures import ProcessPoolExecutor
15
+ from typing import Any, Generic, TypeVar
16
+
17
+ from ..algorithms.external_sort import external_sort
18
+ from ..algorithms.parallel import chunk_is_sorted, sort_chunk
19
+ from ..config import BigArrayConfig
20
+ from ..utils.scale import choose_chunk_size
21
+
22
+ T = TypeVar("T")
23
+
24
+
25
+ class SortMixin(Generic[T]):
26
+ """排序算子 mixin。不声明 __slots__(多继承冲突)。
27
+
28
+ 继承 Generic[T] 仅用于类型推导(让 T 在 mixin 内被绑定);
29
+ 运行时通过 cooperative multiple inheritance 与 _CoreBase 组合,
30
+ _fresh_iter / __len__ / _get_config 实际由 _CoreBase 提供。
31
+ """
32
+
33
+ # 协作契约:实际实现由 _CoreBase 通过 MRO 提供
34
+ def __len__(self) -> int: # pragma: no cover - 协作契约
35
+ raise NotImplementedError
36
+
37
+ def _fresh_iter(self) -> Iterator[T]: # pragma: no cover - 协作契约
38
+ raise NotImplementedError
39
+
40
+ def _get_config(self) -> BigArrayConfig: # pragma: no cover - 协作契约
41
+ raise NotImplementedError
42
+
43
+ def is_sorted(self, max_workers: int | None = None) -> bool:
44
+ """判断数据是否已按升序排列。
45
+
46
+ 全程操作 _fresh_iter(),不碰 self.__data——可重复调用。
47
+ 大数据场景下:
48
+ - 每个 chunk 内部判断局部有序(并行干)
49
+ - 相邻 chunk 之间再比较"前一块尾 ≤ 后一块头"(必要条件)
50
+ 两者同时满足才算全局有序。
51
+ """
52
+ n = len(self)
53
+ if n <= 1:
54
+ return True
55
+
56
+ cfg: BigArrayConfig = self._get_config()
57
+ data = self._fresh_iter()
58
+
59
+ # 小数据:直接流式 O(n) 判断,进程启动开销不划算
60
+ if n < cfg.linear_parallel_threshold:
61
+ prev: T | None = None
62
+ for item in data:
63
+ if prev is not None and prev > item: # type: ignore[operator]
64
+ return False
65
+ prev = item
66
+ return True
67
+
68
+ chunk_size = choose_chunk_size(n)
69
+ chunk_bounds: list[tuple[T, T]] = []
70
+ pool = ProcessPoolExecutor(max_workers=max_workers)
71
+ try:
72
+ futures = []
73
+ chunk: list[T] = []
74
+ for item in data:
75
+ chunk.append(item)
76
+ if len(chunk) >= chunk_size:
77
+ futures.append(pool.submit(chunk_is_sorted, (chunk,)))
78
+ chunk_bounds.append((chunk[0], chunk[-1]))
79
+ chunk = []
80
+ if chunk:
81
+ futures.append(pool.submit(chunk_is_sorted, (chunk,)))
82
+ chunk_bounds.append((chunk[0], chunk[-1]))
83
+
84
+ # 1) 所有 chunk 内部必须有序
85
+ for fut in futures:
86
+ if not fut.result():
87
+ return False
88
+
89
+ # 2) chunk 之间必须有序:第 i 块尾 <= 第 i+1 块头
90
+ for i in range(len(chunk_bounds) - 1):
91
+ if chunk_bounds[i][1] > chunk_bounds[i + 1][0]: # type: ignore[operator]
92
+ return False
93
+
94
+ return True
95
+ finally:
96
+ pool.shutdown(wait=False)
97
+
98
+ def _big_iter_sort(
99
+ self,
100
+ max_workers: int | None = None,
101
+ key: Callable[[T], Any] | None = None,
102
+ ) -> Generator[T, None, None]:
103
+ """大数据排序:按数据量分流。
104
+
105
+ - 小数据(< config.linear_parallel_threshold):直接 sorted() 物化,单进程
106
+ - 中数据(threshold ~ external_sort_threshold):分块并行排序 + 内存多路归并
107
+ - 大数据(>= config.external_sort_threshold):外部排序
108
+ * 默认 streaming 模式:阶段 2 逐条 load,内存峰值 = O(块数)
109
+ * block 模式(external_sort_streaming_merge=False):归并阶段所有块
110
+ 整块 load 到内存,峰值 ≈ 数据总量,但速度快 3~5 倍
111
+
112
+ 参数:
113
+ max_workers: 进程池并行度;None 用默认
114
+ key: 排序键函数(类似 ``sorted(data, key=...)``);None 按元素本身排序
115
+ ⚠️ 多进程路径要求 key 可 pickle(模块级函数或 functools.partial)
116
+
117
+ 全程操作 _fresh_iter()(外部排序内部也用),不碰 self.__data——
118
+ 可以重复调用,不会污染实例状态。
119
+ """
120
+ n = len(self)
121
+ cfg: BigArrayConfig = self._get_config()
122
+
123
+ if n < cfg.linear_parallel_threshold:
124
+ yield from sorted(self._fresh_iter(), key=key) # type: ignore[type-var, arg-type]
125
+ return
126
+
127
+ # 大数据走外部排序(统一走 algorithms.external_sort)
128
+ if n >= cfg.external_sort_threshold:
129
+ yield from external_sort(
130
+ self._fresh_iter(),
131
+ n,
132
+ config=cfg,
133
+ max_workers=max_workers,
134
+ key=key,
135
+ )
136
+ return
137
+
138
+ # 中数据:分块并行排序 + 内存多路归并
139
+ data = self._fresh_iter()
140
+ chunk_size = choose_chunk_size(n)
141
+
142
+ with ProcessPoolExecutor(max_workers=max_workers) as pool:
143
+ futures: list[Any] = []
144
+ chunk: list[T] = []
145
+ for item in data:
146
+ chunk.append(item)
147
+ if len(chunk) >= chunk_size:
148
+ futures.append(pool.submit(sort_chunk, (chunk, key)))
149
+ chunk = []
150
+ if chunk:
151
+ futures.append(pool.submit(sort_chunk, (chunk, key)))
152
+ sorted_chunks = [fut.result() for fut in futures]
153
+
154
+ yield from heapq.merge(*sorted_chunks, key=key)
155
+
156
+ def _external_sort(
157
+ self,
158
+ max_workers: int | None = None,
159
+ key: Callable[[T], Any] | None = None,
160
+ ) -> Generator[T, None, None]:
161
+ """外部排序(向后兼容别名)。
162
+
163
+ 重构后实际实现位于 algorithms.external_sort.external_sort。
164
+ 本方法仅作为旧调用入口(如有人写 self._external_sort(...))。
165
+ """
166
+ cfg: BigArrayConfig = self._get_config()
167
+ yield from external_sort(
168
+ self._fresh_iter(),
169
+ len(self),
170
+ config=cfg,
171
+ max_workers=max_workers,
172
+ key=key,
173
+ )
174
+
175
+ def _binary_search(
176
+ self,
177
+ target: T,
178
+ max_workers: int | None = None,
179
+ key: Callable[[T], Any] | None = None,
180
+ ) -> Generator[tuple[int, T], None, None]:
181
+ """二分查找(先排序,再 bisect)。
182
+
183
+ 正确流程(也是唯一可行的流程):
184
+ 1) 用 _big_iter_sort 做分块并行排序 → 拿到有序迭代器
185
+ 2) 把有序迭代器**物化**成 list(bisect 要求 arr[mid] 随机访问,绕不开)
186
+ 3) 用 bisect_left / bisect_right 夹出 [left, right) 命中区间
187
+ 4) 逐个 yield (排序后下标, 命中元素)
188
+
189
+ 参数:
190
+ target: 要查找的元素;若 key 不为 None,则比较的是 key(target)
191
+ max_workers: 排序阶段进程池并行度
192
+ key: 排序键函数;查找时对 target 和元素都应用 key 后比较
193
+
194
+ 全程操作 _fresh_iter()(在 _big_iter_sort 内部),不碰 self.__data。
195
+ ⚠️ 返回的下标是"**排序后**的下标",不是原始数据的下标。
196
+ """
197
+ sorted_iter: Generator[T, None, None] = self._big_iter_sort(
198
+ max_workers=max_workers, key=key
199
+ )
200
+ sorted_list: list[T] = list(sorted_iter)
201
+ if key is None:
202
+ left = _bisect_mod.bisect_left(sorted_list, target) # type: ignore[call-overload]
203
+ right = _bisect_mod.bisect_right(sorted_list, target) # type: ignore[call-overload]
204
+ else:
205
+ # Python 3.10+ bisect 支持 key 参数(对 list 元素和 target 都应用 key)
206
+ left = _bisect_mod.bisect_left(sorted_list, target, key=key) # type: ignore[call-overload]
207
+ right = _bisect_mod.bisect_right(sorted_list, target, key=key) # type: ignore[call-overload]
208
+ for i in range(left, right):
209
+ yield (i, sorted_list[i])
210
+
211
+
212
+ __all__ = ["SortMixin"]
@@ -0,0 +1,341 @@
1
+ """流式算子 mixin + 链式 API(LazyChain)。
2
+
3
+ 两种 API 共存(向后兼容 + 新功能):
4
+ 1. BigArrayList.map(f) / .filter(p) / .reduce(...) 等
5
+ 返回**裸 generator**——重构前行为,旧调用方零改动。
6
+ 注意:这些方法返回裸 generator,不能 .filter().map() 链式调用。
7
+
8
+ 2. BigArrayList.lazy() → LazyChain
9
+ .lazy().map(f).filter(p).distinct().slice(0, 10).collect()
10
+ 每个算子返回新的 LazyChain(包装变换后的生成器),
11
+ 最终 .collect() / list(...) / for x in chain 物化或迭代。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import functools
17
+ import itertools
18
+ from collections.abc import Callable, Generator, Iterable, Iterator
19
+ from typing import Generic, TypeVar
20
+
21
+ from ..config import BigArrayConfig
22
+ from ..utils.scale import HUNDRED_THOUSAND
23
+
24
+ T = TypeVar("T")
25
+ U = TypeVar("U")
26
+ K = TypeVar("K")
27
+
28
+
29
+ class StreamMixin(Generic[T]):
30
+ """流式算子 mixin:变换 / 过滤 / 聚合 / 切片 / 去重 / 合并 / 分组 / 计数。
31
+
32
+ 全部基于 self._fresh_iter(),不碰 self.__data——可重复调用、零副作用。
33
+ 不声明 __slots__(多继承冲突)。
34
+
35
+ 继承 Generic[T] 仅用于类型推导(让 T 在 mixin 内被绑定);
36
+ 运行时通过 cooperative multiple inheritance 与 _CoreBase 组合,
37
+ _fresh_iter / __len__ / _get_config 实际由 _CoreBase 提供,
38
+ _big_iter_sort 由 SortMixin 提供(MRO 中 SortMixin 在后,调用点用 type:ignore)。
39
+ """
40
+
41
+ # 协作契约:实际实现由 _CoreBase 通过 MRO 提供
42
+ def __len__(self) -> int: # pragma: no cover - 协作契约
43
+ raise NotImplementedError
44
+
45
+ def _fresh_iter(self) -> Iterator[T]: # pragma: no cover - 协作契约
46
+ raise NotImplementedError
47
+
48
+ def _get_config(self) -> BigArrayConfig: # pragma: no cover - 协作契约
49
+ raise NotImplementedError
50
+
51
+ # ==================== 变换 / 过滤 ====================
52
+ def _map_iter(self, func: Callable[[T], U]) -> Generator[U, None, None]:
53
+ """对每个元素应用 func,惰性产出结果。"""
54
+ for item in self._fresh_iter():
55
+ yield func(item)
56
+
57
+ def _filter_iter(self, predicate: Callable[[T], bool]) -> Generator[T, None, None]:
58
+ """过滤:只产出 predicate(item) 为真的元素。"""
59
+ for item in self._fresh_iter():
60
+ if predicate(item):
61
+ yield item
62
+
63
+ def _reduce(self, func: Callable[[U, T], U], initial: U | None = None) -> U:
64
+ """聚合:acc = func(acc, item),逐元素累加,返回最终 acc。
65
+
66
+ - initial 为 None 时用第一个元素作初始 acc(空数据抛 TypeError)
67
+ - initial 不为 None 时,空数据直接返回 initial
68
+ - 内存 O(1)——只维护一个累加器
69
+ """
70
+ it = self._fresh_iter()
71
+ try:
72
+ acc = initial if initial is not None else next(it)
73
+ except StopIteration:
74
+ if initial is not None:
75
+ return initial
76
+ raise TypeError("reduce() of empty iterable with no initial value") from None
77
+ for item in it:
78
+ acc = func(acc, item) # type: ignore[arg-type] # acc 可能为 T(initial=None 用首个元素)
79
+ return acc # type: ignore[return-value] # 同上,T 与 U 的兼容性由调用方保证
80
+
81
+ def _slice_iter(
82
+ self,
83
+ start: int | None,
84
+ stop: int | None,
85
+ step: int | None,
86
+ ) -> Generator[T, None, None]:
87
+ """惰性切片:包装 itertools.islice。
88
+
89
+ 约束(来自 itertools.islice):
90
+ - start/stop/step 不允许负数(惰性流无法反向、无法预知总长倒数)
91
+ - step 必须为正整数或 None
92
+ """
93
+ yield from itertools.islice(self._fresh_iter(), start, stop, step)
94
+
95
+ def _distinct_iter(self) -> Generator[T, None, None]:
96
+ """去重:保留每个元素的首次出现。
97
+
98
+ 按数据量分流:
99
+ - 小数据(< 10 万):set 记录已见元素,O(n) 时间 O(k) 内存
100
+ - 大数据:先 sort 排序,再相邻去重
101
+ O(n log n) 时间,内存可控(排序阶段同 sort)
102
+
103
+ 全程操作 _fresh_iter(),不碰 self.__data。
104
+ """
105
+ n = len(self)
106
+ # 大数据走排序去重路径
107
+ if n >= HUNDRED_THOUSAND:
108
+ # _big_iter_sort 由 SortMixin 提供(MRO 中 SortMixin 在 StreamMixin 之后)
109
+ sorted_iter = self._big_iter_sort() # type: ignore[attr-defined]
110
+ sentinel = object()
111
+ prev: object = sentinel
112
+ for item in sorted_iter:
113
+ if prev is not sentinel and item == prev:
114
+ continue
115
+ yield item
116
+ prev = item
117
+ return
118
+ # 小数据用 set 记录
119
+ seen: set[T] = set()
120
+ for item in self._fresh_iter():
121
+ if item not in seen:
122
+ seen.add(item)
123
+ yield item
124
+
125
+ def _concat_iter(self, other: Iterable[T]) -> Generator[T, None, None]:
126
+ """把 self 和 other 首尾相接,惰性产出。
127
+
128
+ - other 是 BaseBigArrayList 实例:用 other._fresh_iter(),零副作用
129
+ - 其他 Iterable:直接 yield from
130
+ """
131
+ yield from self._fresh_iter()
132
+ # 延迟 import 避免循环
133
+ try:
134
+ from ..core.base import BaseBigArrayList
135
+
136
+ if isinstance(other, BaseBigArrayList):
137
+ yield from other._fresh_iter()
138
+ return
139
+ except ImportError:
140
+ pass
141
+ yield from other
142
+
143
+ def _chain_iter(self, *others: Iterable[T]) -> Generator[T, None, None]:
144
+ """把 self 和多个 others 首尾相接,惰性产出。
145
+
146
+ 对每个 other:BaseBigArrayList 实例走 _fresh_iter()(零副作用),
147
+ 其他 Iterable 直接 yield from。
148
+ """
149
+ yield from self._fresh_iter()
150
+ try:
151
+ from ..core.base import BaseBigArrayList
152
+ except ImportError:
153
+ BaseBigArrayList = None # type: ignore[misc, assignment]
154
+ for o in others:
155
+ if BaseBigArrayList is not None and isinstance(o, BaseBigArrayList):
156
+ yield from o._fresh_iter()
157
+ else:
158
+ yield from o
159
+
160
+ def _group_by_iter(
161
+ self, key_func: Callable[[T], K]
162
+ ) -> Generator[tuple[K, list[T]], None, None]:
163
+ """SQL 风格 GROUP BY:按 key_func 分组,yield (key, list[elements])。
164
+
165
+ 按数据量分流:
166
+ - 小数据(< 10 万):物化 + sorted(key=key_func) → 全局同 key 合并
167
+ - 大数据:流式分组(语义同 itertools.groupby),只合并相邻同 key
168
+ → 要求用户先 sort(key=key_func),否则只合并相邻同 key 的局部
169
+
170
+ ⚠️ 单组元素数量很大时,list(group) 会物化整组到内存。
171
+ """
172
+ n = len(self)
173
+ if n < HUNDRED_THOUSAND:
174
+ data: Iterable[T] = sorted(self._fresh_iter(), key=key_func) # type: ignore[arg-type]
175
+ else:
176
+ data = self._fresh_iter()
177
+ for k, group in itertools.groupby(data, key=key_func):
178
+ yield (k, list(group))
179
+
180
+ def _count_if(self, predicate: Callable[[T], bool]) -> int:
181
+ """流式条件计数:扫一遍数据,对 predicate(item) 为真的元素累加。
182
+
183
+ 只维护一个计数器,O(1) 内存。
184
+ """
185
+ count = 0
186
+ for item in self._fresh_iter():
187
+ if predicate(item):
188
+ count += 1
189
+ return count
190
+
191
+ # ==================== 链式 API 入口 ====================
192
+ def lazy(self) -> LazyChain[T]:
193
+ """切换到链式 API:返回包装 self._fresh_iter() 的 LazyChain。
194
+
195
+ 用法:
196
+ chain = big.lazy() # 进入链式世界
197
+ result = (chain
198
+ .filter(lambda x: x > 0)
199
+ .map(lambda x: x * 2)
200
+ .distinct()
201
+ .slice(0, 10)
202
+ .collect()) # 物化为 list
203
+
204
+ 与旧 .map() / .filter() / ... 的区别:
205
+ - 旧方法:返回裸 generator,不能 .filter().map() 链式
206
+ - lazy():每个算子返回新 LazyChain,链式组合
207
+ """
208
+ return LazyChain(self._fresh_iter())
209
+
210
+
211
+ # ==================== 链式 API(独立类,组合而非继承)====================
212
+ class LazyChain(Generic[T]):
213
+ """惰性链式算子链。
214
+
215
+ 设计要点("组合而非继承"理念):
216
+ - 不继承 BigArrayList,内部组合一个迭代器
217
+ - 每个算子方法返回新 LazyChain(包装变换后的生成器)
218
+ - 全程惰性:只有 .collect() / list(...) / for x in chain 才真正消费
219
+
220
+ 与 BigArrayList.map() 等方法的区别:
221
+ - BigArrayList.map(f) → 裸 generator(向后兼容,不可链式)
222
+ - big.lazy().map(f) → LazyChain(可链式,最后 .collect() 物化)
223
+
224
+ 用法:
225
+ chain = big.lazy()
226
+ out = (chain
227
+ .filter(lambda x: x > 0)
228
+ .map(lambda x: x * 2)
229
+ .slice(0, 100)
230
+ .collect()) # → list
231
+ """
232
+
233
+ __slots__ = ("__iter",)
234
+
235
+ def __init__(self, iterable: Iterable[T]) -> None:
236
+ # 保存迭代器(不保存 BigArrayList,避免循环依赖 + 真正"组合")
237
+ self.__iter = iter(iterable)
238
+
239
+ # ===== 迭代协议(支持 for x in chain / list(chain))=====
240
+ def __iter__(self) -> Iterator[T]:
241
+ return self.__iter
242
+
243
+ # ===== 链式算子 =====
244
+ def map(self, func: Callable[[T], U]) -> LazyChain[U]:
245
+ """对每个元素应用 func,返回新 LazyChain。"""
246
+ return LazyChain(func(x) for x in self)
247
+
248
+ def filter(self, predicate: Callable[[T], bool]) -> LazyChain[T]:
249
+ """过滤 predicate(item) 为真的元素,返回新 LazyChain。"""
250
+ return LazyChain(x for x in self if predicate(x))
251
+
252
+ def slice(
253
+ self,
254
+ start: int | None = None,
255
+ stop: int | None = None,
256
+ step: int | None = None,
257
+ ) -> LazyChain[T]:
258
+ """惰性切片,返回新 LazyChain。
259
+
260
+ 约束:start/stop/step 不允许负数(惰性流无法反向)。
261
+ """
262
+ return LazyChain(itertools.islice(self, start, stop, step))
263
+
264
+ def take(self, n: int) -> LazyChain[T]:
265
+ """取前 n 个元素,返回新 LazyChain。"""
266
+ return LazyChain(itertools.islice(self, n))
267
+
268
+ def drop(self, n: int) -> LazyChain[T]:
269
+ """丢弃前 n 个元素,返回新 LazyChain。"""
270
+ return LazyChain(itertools.islice(self, n, None))
271
+
272
+ def distinct(self) -> LazyChain[T]:
273
+ """去重(保留首次出现),返回新 LazyChain。
274
+
275
+ ⚠️ 仅适合小数据:内部用 set 记录已见元素,内存 O(k)(k = 不同元素数),
276
+ 元素必须可哈希。当不同元素数达到 HUNDRED_THOUSAND(10 万)时主动抛
277
+ RuntimeError 早停,避免大数据无声爆内存。
278
+
279
+ 大数据去重请改用 BigArrayList.distinct()(裸 generator 版,大数据走
280
+ 外部排序去重,内存可控)。
281
+ """
282
+
283
+ def _distinct_gen() -> Generator[T, None, None]:
284
+ seen: set[T] = set()
285
+ for x in self:
286
+ if x in seen:
287
+ continue
288
+ seen.add(x)
289
+ if len(seen) >= HUNDRED_THOUSAND:
290
+ raise RuntimeError(
291
+ f"LazyChain.distinct 的 set 已达 {HUNDRED_THOUSAND} 个不同元素,"
292
+ "继续去重会爆内存;大数据请改用 BigArrayList.distinct()"
293
+ "(走外部排序去重)"
294
+ )
295
+ yield x
296
+
297
+ return LazyChain(_distinct_gen())
298
+
299
+ def concat(self, other: Iterable[T]) -> LazyChain[T]:
300
+ """首尾相接 self 和 other,返回新 LazyChain。"""
301
+ return LazyChain(itertools.chain(self, other))
302
+
303
+ def chain(self, *others: Iterable[T]) -> LazyChain[T]:
304
+ """链式合并多个 others,返回新 LazyChain。"""
305
+ return LazyChain(itertools.chain(self, *others))
306
+
307
+ # ===== 终结算子(消费迭代器,返回值)=====
308
+ def reduce(self, func: Callable[[U, T], U], initial: U | None = None) -> U:
309
+ """聚合:acc = func(acc, item),返回最终 acc。"""
310
+ if initial is not None:
311
+ return functools.reduce(func, self, initial)
312
+ return functools.reduce(func, self) # type: ignore[arg-type, return-value]
313
+
314
+ def count_if(self, predicate: Callable[[T], bool]) -> int:
315
+ """统计 predicate(item) 为真的元素个数。"""
316
+ return sum(1 for x in self if predicate(x))
317
+
318
+ def count(self) -> int:
319
+ """统计元素总数。"""
320
+ return sum(1 for _ in self)
321
+
322
+ def collect(self) -> list[T]:
323
+ """物化为 list(终结算子)。"""
324
+ return list(self)
325
+
326
+ def first(self, default: U | None = None) -> T | U | None:
327
+ """取第一个元素;流为空时返回 default。"""
328
+ for x in self:
329
+ return x
330
+ return default
331
+
332
+ def group_by(self, key_func: Callable[[T], K]) -> LazyChain[tuple[K, list[T]]]:
333
+ """SQL 风格 GROUP BY:按 key_func 分组。
334
+
335
+ 返回新 LazyChain,每项为 (key, list[elements])。
336
+ 要求上游已按 key_func 排序(语义同 itertools.groupby)。
337
+ """
338
+ return LazyChain((k, list(g)) for k, g in itertools.groupby(self, key=key_func))
339
+
340
+
341
+ __all__ = ["LazyChain", "StreamMixin"]
@@ -0,0 +1,45 @@
1
+ """工具层:量级判定 + 参数校验。"""
2
+
3
+ from .scale import (
4
+ HUNDRED_MILLION,
5
+ HUNDRED_THOUSAND,
6
+ MILLION,
7
+ TEN_MILLION,
8
+ ScaleHelper,
9
+ ScaleValue,
10
+ choose_chunk_size,
11
+ choose_external_sort_chunk_size,
12
+ chunk_wise_processing,
13
+ )
14
+ from .validation import (
15
+ validate_callable,
16
+ validate_index,
17
+ validate_iterable,
18
+ validate_max_workers,
19
+ validate_num_buckets,
20
+ validate_range,
21
+ validate_sample_k,
22
+ validate_slice_arg,
23
+ )
24
+
25
+ __all__ = [
26
+ # 量级
27
+ "HUNDRED_MILLION",
28
+ "HUNDRED_THOUSAND",
29
+ "MILLION",
30
+ "TEN_MILLION",
31
+ "ScaleHelper",
32
+ "ScaleValue",
33
+ "choose_chunk_size",
34
+ "choose_external_sort_chunk_size",
35
+ "chunk_wise_processing",
36
+ # 校验
37
+ "validate_callable",
38
+ "validate_index",
39
+ "validate_iterable",
40
+ "validate_max_workers",
41
+ "validate_num_buckets",
42
+ "validate_range",
43
+ "validate_sample_k",
44
+ "validate_slice_arg",
45
+ ]