es-toolkit 函数式编程指南:使用 fp/sampleSize 在 pipe 管道中随机采样
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
sampleSize(函数式编程版)是 es-toolkitfp子模块提供的随机采样算子:它返回一个"等待数据"的函数,可从传入的数组中随机取出指定数量、且不重复数组位置的元素,专为与pipe组合使用而设计。读完本文,你将掌握fp/sampleSize的完整调用签名、边界行为与异常规则,并透过源码理解其基于 Floyd 算法与randomInt的底层实现,以及它与普通版sampleSize的差异和选型依据。
一、函数式变体与普通版的关系
在 es-toolkit 中,普通版sampleSize采用"数据优先"的调用方式(sampleSize(array, size)),而函数式编程版es-toolkit/fp的sampleSize采用"数据置后"(data-last)的柯里化形态,用于在pipe中作为变换步骤使用。
| 形态 | 调用方式 | 适用场景 |
|---|---|---|
| 普通版 | sampleSize(array, size) | 普通代码中直接调用 |
| fp 变体 | pipe(array, sampleSize(size)) | 用pipe组合多个变换步骤时 |
正如docs/fp/reference/sampleSize.md中的提示所强调:在普通代码中应优先使用原始版sampleSize,只有在用pipe组合变换时才使用 fp 变体。fp 变体并不重复实现采样逻辑,而是对普通版的一层薄封装(thin wrapper)。
二、基本用法
import { pipe, sampleSize } from 'es-toolkit/fp'; const values = pipe([1, 2, 3, 4], sampleSize(2)); // values 长度为 2,元素均来自输入数组从src/fp/array/sampleSize.ts的源码可以看到 fp 变体的实现非常简洁:它接收size作为配置参数,返回一个等待数组的函数,该函数在收到数组后转发给普通版实现:
export function sampleSize<T>(size: number): (array: readonly T[]) => T[] { return function (array: readonly T[]): T[] { return sampleSizeToolkit(array, size); }; }这种"先传配置、后传数据"的模式正是es-toolkit/fp的通用设计:每个 fp 函数先接收配置(如map(fn)、take(2)、sampleSize(2)),返回一个接收数据的函数,再由pipe把上一步的输出作为下一步的输入依次贯穿。fp 变体同样通过src/fp/index.ts与src/fp/array/index.ts统一导出。
三、参数与返回值
参数
size(number):要从数组中随机取出的元素个数。
返回值
(array: readonly T[]) => T[]:一个将readonly T[]映射为随机元素数组的函数,即等待pipe喂入数据的变换步骤。
抛出异常
如果size大于传入数组的长度,会抛出错误。
// 抛出 Error: Size must be less than or equal to the length of array. pipe([1, 2, 3], sampleSize(4));四、语义与边界行为
sampleSize返回size个来自输入数组的随机值,不会重复选择同一个数组位置(即无放回采样)。结合普通版文档docs/reference/array/sampleSize.md与测试用例,可以确认以下边界行为:
size为 0:返回空数组[],不会报错;size等于数组长度:等价于洗牌(shuffle)效果,返回一个包含全部元素但顺序随机的新数组——注意它返回的是新数组,与原数组不是同一引用(result.not.toBe(array));size大于数组长度:抛出Error,错误消息为'Size must be less than or equal to the length of array.';- 不修改原数组:函数返回新数组,输入数组保持不变。
这些行为在src/array/sampleSize.spec.ts中均有测试覆盖,例如:
it('returns the same array if the size is equal to the array length', () => { const array = [1, 2, 3]; const result = sampleSize(array, array.length); expect(result).toEqual(array); expect(result).not.toBe(array); });fp 变体的测试src/fp/array/sampleSize.spec.ts则验证了在pipe中使用的效果:结果长度为size,且所有元素均来自输入数组。
五、底层实现原理:Floyd 算法
普通版sampleSize位于src/array/sampleSize.ts,其核心采用Floyd 算法(Robert Floyd 提出的无放回随机采样算法),在保证不重复选中的同时保持高效的线性复杂度:
export function sampleSize<T>(array: readonly T[], size: number): T[] { if (size > array.length) { throw new Error('Size must be less than or equal to the length of array.'); } const result = new Array(size); const selected = new Set(); for (let step = array.length - size, resultIndex = 0; step < array.length; step++, resultIndex++) { let index = randomInt(0, step + 1); if (selected.has(index)) { index = step; } selected.add(index); result[resultIndex] = array[index]; } return result; }算法要点:
- 预分配结果数组:
const result = new Array(size)避免动态扩容; - 去重集合:
const selected = new Set()记录已选中的下标; - 增量随机范围:循环从
array.length - size步进到array.length,每一步用randomInt(0, step + 1)生成一个半开区间[0, step + 1)内的随机整数下标; - 冲突处理:若生成的下标已被选中,则退化为使用当前
step下标,从而保证不重复、且一定能在size步内填满结果; - 随机数来源:
randomInt(见src/math/randomInt.ts)基于random并向下取整,返回[minimum, maximum)之间的随机整数。
由于每一步都能在常数时间内完成(Set的has/add平均为 O(1)),整个采样过程的时间复杂度为 O(n),其中 n 为采样规模size。
六、在 pipe 管道中组合使用
es-toolkit/fp的核心入口是pipe,其实现位于src/fp/pipe.ts。pipe将数据从左到右依次穿过每个函数,把嵌套调用f(g(h(x)))改写为自上而下的顺序步骤。sampleSize(size)返回的正是"等待数据"的一元函数,因此可以自然地插入管道中的任何位置,例如先过滤再采样:
import { pipe, filter, sampleSize } from 'es-toolkit/fp'; // 从 1~100 中先筛出偶数,再随机取 5 个 const result = pipe( Array.from({ length: 100 }, (_, i) => i + 1), filter(x => x % 2 === 0), sampleSize(5) ); // 结果长度为 5,元素均为偶数需要说明的是:sampleSize本身是非惰性(eager)算子,它必须消费完整数组才能完成无放回采样,因此不会参与pipe的惰性融合(lazy fusion)优化路径;当pipe检测到连续惰性算子(如map、filter、take)时才会融合为单趟遍历并支持提前终止(详见src/fp/pipe.ts中的chunkFunctions与lazyPipe)。当sampleSize与其他惰性算子混用时,pipe会按"连续惰性算子组"分段处理,非惰性步骤按普通一元函数逐一执行,行为与直接调用一致。
七、典型应用场景
- 问卷调查抽样:从候选题目或样本池中随机抽取指定数量的条目;
- 游戏随机道具/对手:从物品列表或玩家列表中随机选出多个不重复元素;
- 数据降采样:从大数据集中随机选取子集用于测试或演示;
- 洗牌:令
size等于数组长度,即得到一次完整的随机重排。
八、小结
fp/sampleSize是普通版sampleSize的 contenteditable="false">【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考