最省心方案是:样本量远小于原数组长度时,python用random.sample()、js用fisher-yates改进版;超大规模或内存受限时,须改用索引采样、分块取数或蓄水池算法。

直接用 random.sample()(Python)或 Fisher-Yates 改进版(JS)最省心,前提是样本量远小于原数组长度;若数组极大(如上千万行)、内存受限或需保留原始结构,则必须绕过全量加载,改用索引采样或分块取数。
Python:优先用 random.sample 或 np.random.Generator.choice
对普通列表或中等规模 NumPy 数组,内置方法既简洁又高效:
-
random.sample(list_data, k=100)—— 无放回、不修改原数组、时间复杂度 O(k),适合 Python 原生 list -
rng = np.random.default_rng(42); rng.choice(arr, size=100, replace=False)—— 显式控制随机源,避免全局状态干扰;注意提前检查size ,否则报错 - 二维数组不直接传给
choice,先抽行索引:rows = rng.choice(arr.shape[0], size=10, replace=False); arr[rows]
JavaScript:避开 splice,用“交换截断”法
对大数组(如 10 万+ 元素),别用 Math.random() + splice(),位移开销大。推荐 Fisher-Yates 的采样优化版:
- 只运行 k 次循环,每次从剩余未处理部分随机选一个,与当前下标交换,最后取前 k 个
- 不修改原数组可先拷贝:但若 k ≪ n,更优做法是生成 k 个不重复随机索引(用 Set 控制,k 小时可行;k 大时用蓄水池算法)
- 简单场景可封装:
Array.prototype.sample = function(n) { /* 实现交换截断 */ };
超大规模数据(GB 级):跳过加载,只操作索引
当数组实际是磁盘文件(如 PyArrow 表、Parquet 分区)或流式数据,不能全载入内存:
- PyArrow:用
table.take(indices),其中indices = rng.integers(0, table.num_rows, size=1000, replace=False)—— 零拷贝、不 decode 全量数据 - Pandas:慎用
df.sample(),它会触发to_pandas()加载;改用 Dask 或 Polars 的惰性采样,或先用 Arrow 读取再 take - 自定义流式采样:实现蓄水池算法(Reservoir Sampling),单趟遍历、O(1) 空间,适合无法预知总长的场景
避坑要点:边界、维度、随机源
很多“采样失败”其实不是算法问题,而是配置疏忽:
- NumPy 中
replace=False时,size超出数组长度必报错,务必加守卫逻辑 - 多维数组误传给
np.random.choice会静默展平,要抽行就先操作索引,别依赖自动行为 - 混用
np.random.seed()和default_rng()会导致结果不可复现,统一用后者并显式传 seed - 权重采样必须保证
p参数已归一化,否则结果偏离预期











