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.
- bigarraylist/__init__.py +70 -0
- bigarraylist/_api.py +453 -0
- bigarraylist/algorithms/__init__.py +22 -0
- bigarraylist/algorithms/bucket.py +200 -0
- bigarraylist/algorithms/external_sort.py +200 -0
- bigarraylist/algorithms/parallel.py +68 -0
- bigarraylist/algorithms/sample.py +51 -0
- bigarraylist/config.py +86 -0
- bigarraylist/core/__init__.py +5 -0
- bigarraylist/core/base.py +223 -0
- bigarraylist/exceptions.py +50 -0
- bigarraylist/operators/__init__.py +14 -0
- bigarraylist/operators/crud.py +89 -0
- bigarraylist/operators/search.py +212 -0
- bigarraylist/operators/sort.py +212 -0
- bigarraylist/operators/stream.py +341 -0
- bigarraylist/utils/__init__.py +45 -0
- bigarraylist/utils/scale.py +139 -0
- bigarraylist/utils/validation.py +171 -0
- bigarraylist-0.2.0.dist-info/METADATA +158 -0
- bigarraylist-0.2.0.dist-info/RECORD +24 -0
- bigarraylist-0.2.0.dist-info/WHEEL +5 -0
- bigarraylist-0.2.0.dist-info/licenses/LICENSE +21 -0
- bigarraylist-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""BaseBigArrayList:迭代协议 + _fresh_iter + __slots__。
|
|
2
|
+
|
|
3
|
+
通过 cooperative multiple inheritance 组合各算子 mixin:
|
|
4
|
+
BaseBigArrayList(_CoreBase, CRUDMixin, StreamMixin, SortMixin, SearchMixin)
|
|
5
|
+
|
|
6
|
+
各 mixin 只声明方法、不声明字段(避免 __slots__ 多继承冲突),
|
|
7
|
+
通过 self._fresh_iter() 和 len(self) 与 _CoreBase 协作。
|
|
8
|
+
蓄水池抽样 _sample 直接写在 _CoreBase 里(依赖 reservoir_sample 函数即可,无需 mixin)。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Generator, Iterable, Iterator
|
|
14
|
+
from typing import Any, Generic, TypeVar
|
|
15
|
+
|
|
16
|
+
from ..algorithms.sample import reservoir_sample
|
|
17
|
+
from ..config import BigArrayConfig
|
|
18
|
+
from ..operators.crud import CRUDMixin
|
|
19
|
+
from ..operators.search import SearchMixin
|
|
20
|
+
from ..operators.sort import SortMixin
|
|
21
|
+
from ..operators.stream import StreamMixin
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _CoreBase(Generic[T]):
|
|
27
|
+
"""核心:__slots__ + 迭代协议 + _fresh_iter。
|
|
28
|
+
|
|
29
|
+
所有算子方法(_append_iter / find / sort / ...)由各 mixin 提供,
|
|
30
|
+
它们统一通过本类的 _fresh_iter() 和 len(self) 与核心交互,
|
|
31
|
+
不直接访问 self.__source / self.__len 等私有字段——
|
|
32
|
+
避免 name mangling 在 mixin 模式下找不到属性。
|
|
33
|
+
|
|
34
|
+
继承 Generic[T] 让 T 在本类内被绑定,使 _fresh_iter / __next__ 等方法
|
|
35
|
+
的返回类型 T 能被 mypy 推导;运行时 Generic 不引入额外开销。
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
__slots__ = ("__config", "__data", "__index", "__len", "__source")
|
|
39
|
+
|
|
40
|
+
# 类型标注(mypy 用,运行时被 __slots__ 覆盖)
|
|
41
|
+
__config: BigArrayConfig
|
|
42
|
+
__data: Iterator[Any]
|
|
43
|
+
__index: int
|
|
44
|
+
__len: int
|
|
45
|
+
__source: int | Iterable[Any]
|
|
46
|
+
|
|
47
|
+
def __init__(self, source: int | Iterable[T], *, config: BigArrayConfig | None = None) -> None:
|
|
48
|
+
"""三种构造方式:
|
|
49
|
+
- source 是 int:用生成器造 source 个全 0 数据(大数据惰性测试用)
|
|
50
|
+
- source 是 Sized Iterable(list/tuple 等有 __len__ 的):直接用其 len
|
|
51
|
+
- source 是纯 Iterator/Generator(没 len 的):**必须物化才能知道长度**,
|
|
52
|
+
大数据场景不推荐——传进来前最好先过 list(...) 或者自己算好长度传 int
|
|
53
|
+
"""
|
|
54
|
+
# 保存配置(None 用默认)
|
|
55
|
+
self.__config = config if config is not None else BigArrayConfig.default()
|
|
56
|
+
|
|
57
|
+
# 保存原始 source,供重置迭代器用
|
|
58
|
+
self.__source = source
|
|
59
|
+
if isinstance(source, int):
|
|
60
|
+
self.__data = iter(0 for _ in range(source))
|
|
61
|
+
self.__len = source
|
|
62
|
+
else:
|
|
63
|
+
try:
|
|
64
|
+
# 优先走 __len__ 路径(list/tuple/str 等容器,零物化)
|
|
65
|
+
data_len = len(source) # type: ignore[arg-type]
|
|
66
|
+
except TypeError:
|
|
67
|
+
# 没 __len__ 的可迭代对象(包括 generator):必须物化拿长度
|
|
68
|
+
materialized = list(source)
|
|
69
|
+
# ✅ 关键修复:source 已在 list() 时耗尽,必须更新 __source 为物化后的 list,
|
|
70
|
+
# 否则 _fresh_iter() 会返回空迭代器,所有算子产出空数据
|
|
71
|
+
self.__source = materialized
|
|
72
|
+
self.__data = iter(materialized)
|
|
73
|
+
self.__len = len(materialized)
|
|
74
|
+
else:
|
|
75
|
+
# 也要兼容 Generator(即使有 __len__ 也不能复用)
|
|
76
|
+
from collections.abc import Generator as _Gen
|
|
77
|
+
|
|
78
|
+
if isinstance(source, _Gen):
|
|
79
|
+
materialized = list(source)
|
|
80
|
+
self.__source = materialized
|
|
81
|
+
self.__data = iter(materialized)
|
|
82
|
+
self.__len = len(materialized)
|
|
83
|
+
else:
|
|
84
|
+
self.__data = iter(source)
|
|
85
|
+
self.__len = data_len
|
|
86
|
+
self.__index = 0 # 当前已消费的位置(惰性迭代用)
|
|
87
|
+
|
|
88
|
+
# ===== 长度 / 索引 =====
|
|
89
|
+
def __len__(self) -> int:
|
|
90
|
+
return self.__len
|
|
91
|
+
|
|
92
|
+
def __getitem__(self, index: int) -> T:
|
|
93
|
+
# 大数据不支持随机访问:随机访问要求把数据全部物化进内存,
|
|
94
|
+
# 这与"惰性流"的核心设计冲突。要用第 N 个元素,请用 itertools.islice 流式跳过
|
|
95
|
+
raise NotImplementedError(
|
|
96
|
+
"Index access is not supported for big data, use itertools.islice instead"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# ===== 迭代协议 =====
|
|
100
|
+
def __iter__(self) -> Iterator[T]:
|
|
101
|
+
# 惰性迭代:返回 self,让 for 循环调用 __next__ 逐个取值
|
|
102
|
+
# 不预读任何数据,next 一次产出一个
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def __next__(self) -> T:
|
|
106
|
+
# 标准 Python 迭代器协议:耗尽抛 StopIteration。
|
|
107
|
+
# 这样 `for x in big` / `list(big)` 都能正常结束,
|
|
108
|
+
# 不会因为循环语义而卡死。
|
|
109
|
+
try:
|
|
110
|
+
item = next(self.__data)
|
|
111
|
+
except StopIteration:
|
|
112
|
+
raise
|
|
113
|
+
self.__index += 1
|
|
114
|
+
return item # type: ignore[no-any-return] # __data 持 Any,运行时为 T
|
|
115
|
+
|
|
116
|
+
# ===== 配置访问(mixin 用)=====
|
|
117
|
+
def _get_config(self) -> BigArrayConfig:
|
|
118
|
+
"""返回实例的 BigArrayConfig(供算子读取阈值)。"""
|
|
119
|
+
return self.__config
|
|
120
|
+
|
|
121
|
+
# ===== 核心机制:独立迭代器 =====
|
|
122
|
+
def _fresh_iter(self) -> Iterator[T]:
|
|
123
|
+
"""返回一份基于原始 source 的全新独立迭代器。
|
|
124
|
+
|
|
125
|
+
所有算子(_append_iter / _remove_iter / find / sort / ...)
|
|
126
|
+
统一通过本方法拿一份**独立**的局部迭代器,全程操作局部变量,
|
|
127
|
+
绝不碰 self.__data——避免算子之间互相污染实例状态。
|
|
128
|
+
|
|
129
|
+
self.__data 只留给 __next__ 对外 for 循环用。
|
|
130
|
+
"""
|
|
131
|
+
src = self.__source
|
|
132
|
+
if isinstance(src, int):
|
|
133
|
+
# int source:生成全 0 流(int 兼容 T 由调用方语义保证,类型层无法表达)
|
|
134
|
+
return iter(0 for _ in range(src)) # type: ignore[misc]
|
|
135
|
+
return iter(src)
|
|
136
|
+
|
|
137
|
+
def cycle(self) -> Generator[T, None, None]:
|
|
138
|
+
"""无限循环迭代器:数据耗尽后从头再来,永不停止。
|
|
139
|
+
|
|
140
|
+
想要"耗尽不停止"的循环语义时用这个,而不是直接 for x in big。
|
|
141
|
+
用法:
|
|
142
|
+
for x in itertools.islice(big.cycle(), 100): # 只取 100 个
|
|
143
|
+
...
|
|
144
|
+
for x in big.cycle(): # 真无限循环,必须自己 break
|
|
145
|
+
if cond: break
|
|
146
|
+
"""
|
|
147
|
+
while True:
|
|
148
|
+
yield from self._fresh_iter()
|
|
149
|
+
|
|
150
|
+
def __contains__(self, target: object) -> bool:
|
|
151
|
+
"""x in big_arr:线性查找 target 是否存在于数据中。
|
|
152
|
+
|
|
153
|
+
⚠️ 关键设计:用 _fresh_iter() 拿**独立**迭代器查找,
|
|
154
|
+
不消费 self.__data——算子调用零副作用,可以重复调用。
|
|
155
|
+
找不到时返回 False(耗尽的是临时迭代器,不影响实例状态)。
|
|
156
|
+
"""
|
|
157
|
+
return any(item == target for item in self._fresh_iter())
|
|
158
|
+
|
|
159
|
+
# ===== 反向 / 复制(与 source 类型强耦合,放核心层)=====
|
|
160
|
+
def _reverse_iter(self) -> Generator[T, None, None]:
|
|
161
|
+
"""反向迭代器:从尾到头逐个产出元素。
|
|
162
|
+
|
|
163
|
+
⚠️ 反向迭代天然要求"先看到尾才能倒着产头",对非 int 源
|
|
164
|
+
必须物化源数据——这是反向语义的固有代价,不是设计缺陷。
|
|
165
|
+
|
|
166
|
+
按 source 类型分流:
|
|
167
|
+
- int source(全 0 测试源):反转仍是全 0,直接生成 N 个 0
|
|
168
|
+
- list/tuple/str 等 reversible 容器:reversed(src) 视图
|
|
169
|
+
- generator source(已耗尽):抛 TypeError
|
|
170
|
+
"""
|
|
171
|
+
src = self.__source
|
|
172
|
+
if isinstance(src, int):
|
|
173
|
+
for _ in range(src):
|
|
174
|
+
yield 0 # type: ignore[misc] # int 兼容 T 由调用方语义保证
|
|
175
|
+
return
|
|
176
|
+
try:
|
|
177
|
+
yield from reversed(src) # type: ignore[call-overload] # src 运行时是 Reversible 容器
|
|
178
|
+
except TypeError as e:
|
|
179
|
+
raise TypeError(
|
|
180
|
+
"无法对已耗尽的 generator source 反向迭代;请用 list/tuple 构造以支持 reverse"
|
|
181
|
+
) from e
|
|
182
|
+
|
|
183
|
+
def _copy_iter(self) -> Generator[T, None, None]:
|
|
184
|
+
"""复制迭代器:从头到尾逐个产出元素。
|
|
185
|
+
|
|
186
|
+
全程操作 _fresh_iter(),不碰 self.__data。
|
|
187
|
+
"""
|
|
188
|
+
yield from self._fresh_iter()
|
|
189
|
+
|
|
190
|
+
# ===== 供子类(_api.BigArrayList)访问原始 source(copy/构造新实例) =====
|
|
191
|
+
def _get_source(self) -> int | Iterable[Any]:
|
|
192
|
+
"""返回原始 source(int / list / tuple / ...)。
|
|
193
|
+
|
|
194
|
+
供 BigArrayList.copy() 等需要构造新实例的场景使用。
|
|
195
|
+
返回的是原始引用,不复制——调用方请勿修改。
|
|
196
|
+
"""
|
|
197
|
+
return self.__source
|
|
198
|
+
|
|
199
|
+
# ===== 蓄水池抽样(直接放核心层,无需 mixin)=====
|
|
200
|
+
def _sample(self, k: int, seed: int | None = None) -> Generator[T, None, None]:
|
|
201
|
+
"""蓄水池抽样:从总量未知的流里随机等概率抽 k 个元素。
|
|
202
|
+
|
|
203
|
+
数学保证:每个元素被选中的概率都是 k/N(N 为实际总量,无需预先知道)。
|
|
204
|
+
内存 O(k),无需预知 N。
|
|
205
|
+
|
|
206
|
+
参数:
|
|
207
|
+
k: 要抽取的样本数(必须 >= 1,调用方校验)
|
|
208
|
+
seed: 随机种子,None 表示系统随机;传 int 可复现结果
|
|
209
|
+
"""
|
|
210
|
+
yield from reservoir_sample(self._fresh_iter(), k, seed=seed)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class BaseBigArrayList(_CoreBase[T], CRUDMixin[T], StreamMixin[T], SortMixin[T], SearchMixin[T]):
|
|
214
|
+
"""大数据惰性流基类:组合核心协议 + 所有算子 mixin。
|
|
215
|
+
|
|
216
|
+
对外 API 由 _api.BigArrayList 提供(继承本类 + 参数校验)。
|
|
217
|
+
本类供"需要直接访问底层算子、不要参数校验"的场景使用。
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
__all__ = ["BaseBigArrayList", "BigArrayConfig", "_CoreBase", "reservoir_sample"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""自定义异常层次。
|
|
2
|
+
|
|
3
|
+
向后兼容策略:所有自定义异常都继承自 ValueError / TypeError / IndexError,
|
|
4
|
+
现有 `except ValueError:` / `except TypeError:` / `except IndexError:` 调用方代码
|
|
5
|
+
无需任何修改即可继续工作;想精确捕获的调用方可改用 BigArrayListError 子类。
|
|
6
|
+
|
|
7
|
+
异常层次:
|
|
8
|
+
|
|
9
|
+
BigArrayListError ← 所有自定义异常的根
|
|
10
|
+
├── InvalidArgumentError(ValueError) 参数类型/取值不合法
|
|
11
|
+
├── IndexOutOfRangeError(IndexError) 索引越界(start/end/index)
|
|
12
|
+
└── DataSourceError(TypeError) source 类型不支持某操作(reverse/copy generator 源等)
|
|
13
|
+
|
|
14
|
+
未来如有更细的需求,可继续在此层次下加子类。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BigArrayListError(Exception):
|
|
21
|
+
"""所有 BigArrayList 自定义异常的根。
|
|
22
|
+
|
|
23
|
+
继承 Exception 而非 ValueError/TypeError:本类是包内统一标识。
|
|
24
|
+
具体子类按语义分别继承 ValueError/IndexError/TypeError 以保证向后兼容。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class InvalidArgumentError(BigArrayListError, ValueError):
|
|
29
|
+
"""参数类型/取值不合法(如 max_workers < 1、num_buckets 为负数、step=0)。"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class IndexOutOfRangeError(BigArrayListError, IndexError):
|
|
33
|
+
"""索引越界(start/end/index 超出 [0, len] 范围)。"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DataSourceError(BigArrayListError, TypeError):
|
|
37
|
+
"""source 类型不支持某操作。
|
|
38
|
+
|
|
39
|
+
例:
|
|
40
|
+
- generator source 已被耗尽,无法 reverse / copy
|
|
41
|
+
- 不可哈希元素走小数据 distinct 分支
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"BigArrayListError",
|
|
47
|
+
"DataSourceError",
|
|
48
|
+
"IndexOutOfRangeError",
|
|
49
|
+
"InvalidArgumentError",
|
|
50
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""算子层:CRUD / 排序 / 查找 / 流式 + 链式 API。"""
|
|
2
|
+
|
|
3
|
+
from .crud import CRUDMixin
|
|
4
|
+
from .search import SearchMixin
|
|
5
|
+
from .sort import SortMixin
|
|
6
|
+
from .stream import LazyChain, StreamMixin
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"CRUDMixin",
|
|
10
|
+
"LazyChain",
|
|
11
|
+
"SearchMixin",
|
|
12
|
+
"SortMixin",
|
|
13
|
+
"StreamMixin",
|
|
14
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""CRUD 算子 mixin:惰性增 / 删 / 改 / 插。
|
|
2
|
+
|
|
3
|
+
所有方法全程操作 self._fresh_iter() 返回的局部迭代器,不碰 self.__data——
|
|
4
|
+
算子调用零副作用、可重复调用。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Generator, Iterator
|
|
10
|
+
from typing import Generic, TypeVar
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CRUDMixin(Generic[T]):
|
|
16
|
+
"""增删改插算子 mixin。
|
|
17
|
+
|
|
18
|
+
不声明 __slots__:避免与 _CoreBase 的 __slots__ 多继承冲突;
|
|
19
|
+
所有状态字段全部由 _CoreBase 持有,本 mixin 只读访问通过 self._fresh_iter()。
|
|
20
|
+
|
|
21
|
+
继承 Generic[T] 仅用于类型推导(让 T 在 mixin 内被绑定);
|
|
22
|
+
运行时通过 cooperative multiple inheritance 与 _CoreBase 组合,
|
|
23
|
+
_fresh_iter / __len__ 等方法实际由 _CoreBase 提供。
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# 类型契约:实际实现由 _CoreBase 通过 MRO 提供
|
|
27
|
+
def _fresh_iter(self) -> Iterator[T]: # pragma: no cover - 协作契约,由 _CoreBase 实现
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
|
|
30
|
+
def _append_iter(self, *new_items: T) -> Generator[T, None, None]:
|
|
31
|
+
"""惰性追加:在原流末尾接上新元素,返回新生成器。
|
|
32
|
+
|
|
33
|
+
全程操作 _fresh_iter() 返回的局部迭代器,不碰 self.__data——
|
|
34
|
+
可以重复调用,不会污染实例状态。
|
|
35
|
+
"""
|
|
36
|
+
data = self._fresh_iter()
|
|
37
|
+
yield from data
|
|
38
|
+
yield from new_items
|
|
39
|
+
|
|
40
|
+
def _replace_iter(self, start: int, end: int, *new_items: T) -> Generator[T, None, None]:
|
|
41
|
+
"""惰性替换:把 [start, end) 范围的元素替换成 new_items。
|
|
42
|
+
|
|
43
|
+
全程操作 _fresh_iter() 返回的局部迭代器,不碰 self.__data。
|
|
44
|
+
"""
|
|
45
|
+
data = self._fresh_iter()
|
|
46
|
+
# 产出 0~start-1
|
|
47
|
+
for _ in range(start):
|
|
48
|
+
yield next(data)
|
|
49
|
+
# 丢弃 start ~ end-1
|
|
50
|
+
drop_count = end - start
|
|
51
|
+
for _ in range(drop_count):
|
|
52
|
+
next(data)
|
|
53
|
+
# 输出新内容
|
|
54
|
+
yield from new_items
|
|
55
|
+
# 输出剩余
|
|
56
|
+
yield from data
|
|
57
|
+
|
|
58
|
+
def _insert_iter(self, index: int, *new_items: T) -> Generator[T, None, None]:
|
|
59
|
+
"""在指定位置插入新元素的迭代器。
|
|
60
|
+
|
|
61
|
+
全程操作 _fresh_iter() 返回的局部迭代器,不碰 self.__data。
|
|
62
|
+
"""
|
|
63
|
+
data = self._fresh_iter()
|
|
64
|
+
# 产出 0~index-1
|
|
65
|
+
for _ in range(index):
|
|
66
|
+
yield next(data)
|
|
67
|
+
# 产出新增的元素
|
|
68
|
+
yield from new_items
|
|
69
|
+
# 产出剩余元素
|
|
70
|
+
yield from data
|
|
71
|
+
|
|
72
|
+
def _remove_iter(self, start: int, end: int) -> Generator[T, None, None]:
|
|
73
|
+
"""惰性删除:跳过 [start, end) 范围的元素,其余照常产出。
|
|
74
|
+
|
|
75
|
+
全程操作 _fresh_iter() 返回的局部迭代器,不碰 self.__data。
|
|
76
|
+
"""
|
|
77
|
+
data = self._fresh_iter()
|
|
78
|
+
# 产出 0~start-1
|
|
79
|
+
for _ in range(start):
|
|
80
|
+
yield next(data)
|
|
81
|
+
# 丢弃 start ~ end-1
|
|
82
|
+
drop_count = end - start
|
|
83
|
+
for _ in range(drop_count):
|
|
84
|
+
next(data)
|
|
85
|
+
# 产出剩余元素
|
|
86
|
+
yield from data
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = ["CRUDMixin"]
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""查找算子 mixin:并行线性查找 / 二分查找 / 持久化分桶查找 / 二级索引 / 自动查找入口。
|
|
2
|
+
|
|
3
|
+
核心改进(对比重构前):
|
|
4
|
+
- _bucket_find 改为复用 algorithms.bucket.build_buckets,
|
|
5
|
+
持久化 BucketIndex 让多次查询真正复用(重构前每次重建桶)。
|
|
6
|
+
- 新增 _build_buckets() 公共方法,返回 BucketIndex 给用户跨多次 find 复用。
|
|
7
|
+
- 阈值改走 BigArrayConfig(self._get_config())。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable, Generator, Iterator
|
|
13
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
14
|
+
from typing import Any, Generic, TypeVar
|
|
15
|
+
|
|
16
|
+
from ..algorithms.bucket import BucketIndex, build_buckets
|
|
17
|
+
from ..algorithms.parallel import linear_find_in_chunk
|
|
18
|
+
from ..config import BigArrayConfig
|
|
19
|
+
from ..utils.scale import choose_chunk_size
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SearchMixin(Generic[T]):
|
|
25
|
+
"""查找算子 mixin。不声明 __slots__。
|
|
26
|
+
|
|
27
|
+
继承 Generic[T] 仅用于类型推导;运行时通过 cooperative multiple
|
|
28
|
+
inheritance 与 _CoreBase 组合,_fresh_iter / __len__ / _get_config
|
|
29
|
+
实际由 _CoreBase 提供(MRO 中 _CoreBase 在前,stub 被覆盖)。
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# 协作契约:实际实现由 _CoreBase 通过 MRO 提供
|
|
33
|
+
def __len__(self) -> int: # pragma: no cover - 协作契约
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
def _fresh_iter(self) -> Iterator[T]: # pragma: no cover - 协作契约
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
def _get_config(self) -> BigArrayConfig: # pragma: no cover - 协作契约
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def _parallel_find(
|
|
43
|
+
self,
|
|
44
|
+
target: T,
|
|
45
|
+
max_workers: int | None = None,
|
|
46
|
+
) -> Generator[tuple[int, T], None, None]:
|
|
47
|
+
"""并行分块线性查找(惰性生成器):每个进程负责一个 chunk 的线性查找。
|
|
48
|
+
|
|
49
|
+
- 数据量 < config.linear_parallel_threshold:直接线性 yield(不开进程)
|
|
50
|
+
- 数据量 >= threshold:流式物化分块 + 多进程并行,按块提交顺序 yield
|
|
51
|
+
- 惰性:不收集所有结果到 list,调用方 next 一次产出一个 (下标, 元素)
|
|
52
|
+
"""
|
|
53
|
+
n = len(self)
|
|
54
|
+
cfg: BigArrayConfig = self._get_config()
|
|
55
|
+
data = self._fresh_iter()
|
|
56
|
+
|
|
57
|
+
# 小数据:进程启动开销 > 收益,直接线性 yield
|
|
58
|
+
if n < cfg.linear_parallel_threshold:
|
|
59
|
+
for i, item in enumerate(data):
|
|
60
|
+
if item == target:
|
|
61
|
+
yield (i, item)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
chunk_size = choose_chunk_size(n)
|
|
65
|
+
|
|
66
|
+
# 注意:不能用 with 块——生成器要在 with 外 yield,
|
|
67
|
+
# 而 with 退出会关池。所以手动管理生命周期:try/finally 在生成器结束时 shutdown。
|
|
68
|
+
pool = ProcessPoolExecutor(max_workers=max_workers)
|
|
69
|
+
try:
|
|
70
|
+
futures = []
|
|
71
|
+
chunk: list[T] = []
|
|
72
|
+
current_index = 0
|
|
73
|
+
for item in data:
|
|
74
|
+
chunk.append(item)
|
|
75
|
+
if len(chunk) >= chunk_size:
|
|
76
|
+
futures.append(
|
|
77
|
+
pool.submit(linear_find_in_chunk, (chunk, target, current_index))
|
|
78
|
+
)
|
|
79
|
+
current_index += len(chunk)
|
|
80
|
+
chunk = []
|
|
81
|
+
if chunk:
|
|
82
|
+
futures.append(pool.submit(linear_find_in_chunk, (chunk, target, current_index)))
|
|
83
|
+
|
|
84
|
+
# 按块提交顺序 yield 结果(保留下标单调递增)
|
|
85
|
+
for fut in futures:
|
|
86
|
+
yield from fut.result()
|
|
87
|
+
finally:
|
|
88
|
+
pool.shutdown(wait=False)
|
|
89
|
+
|
|
90
|
+
def _bucket_find(
|
|
91
|
+
self,
|
|
92
|
+
target: T,
|
|
93
|
+
key_func: Callable[[T], Any] | None = None,
|
|
94
|
+
num_buckets: int | None = None,
|
|
95
|
+
max_workers: int | None = None,
|
|
96
|
+
) -> Generator[tuple[int, T], None, None]:
|
|
97
|
+
"""分桶查找:按 key 的哈希值把数据流式分到 N 个桶文件,
|
|
98
|
+
只扫目标桶即可定位所有命中元素,避免全量线性扫描。
|
|
99
|
+
|
|
100
|
+
重构后改进:内部走 algorithms.bucket.build_buckets 拿到 BucketIndex,
|
|
101
|
+
再调 BucketIndex.find(target)。多次查询应直接用 build_buckets 复用。
|
|
102
|
+
本方法每次调用都重建桶——保持零副作用、向后兼容。
|
|
103
|
+
"""
|
|
104
|
+
n = len(self)
|
|
105
|
+
cfg: BigArrayConfig = self._get_config()
|
|
106
|
+
|
|
107
|
+
# 小数据:不分桶,直接线性扫(建桶开销不划算)
|
|
108
|
+
if n < cfg.linear_parallel_threshold:
|
|
109
|
+
kf = key_func if key_func is not None else (lambda x: x)
|
|
110
|
+
target_key = kf(target)
|
|
111
|
+
for i, item in enumerate(self._fresh_iter()):
|
|
112
|
+
if kf(item) == target_key:
|
|
113
|
+
yield (i, item)
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
# 大数据:建桶 + 单桶扫(建完即查即弃;多次查询请直接用 build_buckets)
|
|
117
|
+
idx = build_buckets(
|
|
118
|
+
self._fresh_iter(),
|
|
119
|
+
n,
|
|
120
|
+
key_func=key_func,
|
|
121
|
+
num_buckets=num_buckets,
|
|
122
|
+
config=cfg,
|
|
123
|
+
)
|
|
124
|
+
try:
|
|
125
|
+
yield from idx.find(target)
|
|
126
|
+
finally:
|
|
127
|
+
idx.close()
|
|
128
|
+
|
|
129
|
+
def _build_buckets(
|
|
130
|
+
self,
|
|
131
|
+
key_func: Callable[[T], Any] | None = None,
|
|
132
|
+
num_buckets: int | None = None,
|
|
133
|
+
) -> BucketIndex:
|
|
134
|
+
"""建持久化分桶索引,返回 BucketIndex。
|
|
135
|
+
|
|
136
|
+
与 _bucket_find 的区别:
|
|
137
|
+
- _bucket_find:建桶 + 查一次 + 销毁桶,仅适合单次查
|
|
138
|
+
- _build_buckets:建桶一次返回 BucketIndex,可多次 find(target),
|
|
139
|
+
跨多次查询真正复用建桶成本。
|
|
140
|
+
|
|
141
|
+
使用:
|
|
142
|
+
with big._build_buckets(key_func=lambda u: u.id) as idx:
|
|
143
|
+
for tgt in targets:
|
|
144
|
+
hits = list(idx.find(tgt))
|
|
145
|
+
"""
|
|
146
|
+
n = len(self)
|
|
147
|
+
cfg: BigArrayConfig = self._get_config()
|
|
148
|
+
return build_buckets(
|
|
149
|
+
self._fresh_iter(),
|
|
150
|
+
n,
|
|
151
|
+
key_func=key_func,
|
|
152
|
+
num_buckets=num_buckets,
|
|
153
|
+
config=cfg,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _build_index(self, key_func: Callable[[T], Any] | None = None) -> dict:
|
|
157
|
+
"""建二级索引:dict[key] -> list[原始下标]。
|
|
158
|
+
|
|
159
|
+
内存够用时用 dict,O(n) 时间 O(n) 内存。建好的索引可反复用:
|
|
160
|
+
- 判断 key 在不在:O(1)
|
|
161
|
+
- 拿到所有命中下标列表:O(1)
|
|
162
|
+
|
|
163
|
+
注意:惰性流不支持随机访问(__getitem__ 抛 NotImplementedError),
|
|
164
|
+
所以索引存的是下标,要拿元素仍需流式扫。索引的核心价值是
|
|
165
|
+
"建目录快速定位下标",省去线性扫判断 key 在不在的 O(n) 开销。
|
|
166
|
+
|
|
167
|
+
约束:key_func 必须满足 a==b → key(a)==key(b),
|
|
168
|
+
且 hash(key(a)) == hash(key(b)),否则索引会漏命中。
|
|
169
|
+
|
|
170
|
+
全程操作 _fresh_iter(),不碰 self.__data——可重复调用。
|
|
171
|
+
"""
|
|
172
|
+
if key_func is None:
|
|
173
|
+
key_func = lambda x: x # noqa: E731
|
|
174
|
+
index: dict[Any, list[int]] = {}
|
|
175
|
+
for i, item in enumerate(self._fresh_iter()):
|
|
176
|
+
k = key_func(item)
|
|
177
|
+
if k in index:
|
|
178
|
+
index[k].append(i)
|
|
179
|
+
else:
|
|
180
|
+
index[k] = [i]
|
|
181
|
+
return index
|
|
182
|
+
|
|
183
|
+
def _auto_find(
|
|
184
|
+
self,
|
|
185
|
+
target: T,
|
|
186
|
+
key_func: Callable[[T], Any] | None = None,
|
|
187
|
+
num_buckets: int | None = None,
|
|
188
|
+
max_workers: int | None = None,
|
|
189
|
+
) -> Generator[tuple[int, T], None, None]:
|
|
190
|
+
"""统一查找入口:根据数据量自动选策略。
|
|
191
|
+
|
|
192
|
+
策略:
|
|
193
|
+
- 小数据(< config.linear_parallel_threshold):直接线性查找
|
|
194
|
+
- 大数据(>= threshold):分桶查找(建桶 + 单桶扫)
|
|
195
|
+
|
|
196
|
+
不做 binary_search(要排序+返回排序后下标,语义不同)。
|
|
197
|
+
不缓存预处理结果(零副作用;查很多次请直接用 build_buckets)。
|
|
198
|
+
"""
|
|
199
|
+
n = len(self)
|
|
200
|
+
cfg: BigArrayConfig = self._get_config()
|
|
201
|
+
if n < cfg.linear_parallel_threshold:
|
|
202
|
+
yield from self._parallel_find(target, max_workers=max_workers)
|
|
203
|
+
else:
|
|
204
|
+
yield from self._bucket_find(
|
|
205
|
+
target,
|
|
206
|
+
key_func=key_func,
|
|
207
|
+
num_buckets=num_buckets,
|
|
208
|
+
max_workers=max_workers,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
__all__ = ["SearchMixin"]
|