es-toolkit 组合函数 combinations 详解从 API 到源码级实现【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit导读combinations是 es-toolkit 数组模块中用于生成「组合」combination的核心工具函数它从输入数组中取出所有长度为r的子集且不考虑元素顺序。本文以 docs/ja/reference/array/combinations.md 为骨架结合 src/array/combinations.ts 的源码实现、src/array/combinations.spec.ts 的测试用例以及函数式FP变体的封装带你掌握该函数的完整 API、边界行为、底层算法原理与实际应用场景。一、函数签名与基本概念1.1 调用形式const result combinations(arr, r);combinations返回从数组arr中按任意顺序选择r个元素的所有组合。组合数量遵循标准组合数公式当0 r n时组合总数为n! / r! / (n - r)!其中n arr.length当r n时组合总数为0。1.2 接口定义参数参数类型说明arrreadonly T[]从中选取元素的输入数组rnumber每个组合的长度必须是 0 或正整数返回值类型T[][]返回所有长度为r的组合数组。抛错当r不是非负整数时如负数、小数、NaN抛出Error。1.3 从源码确认签名在 src/array/combinations.ts 中函数实现的第一行即是对r的合法性校验export function combinationsT(arr: readonly T[], r: number): T[][] { if (!Number.isInteger(r) || r 0) { throw new Error(r must be a non-negative integer.); } ... }这里使用了Number.isInteger进行双重约束既要求r是整数又要求其非负。对应测试 src/array/combinations.spec.ts 验证了r -1、r 1.5、r NaN三种非法输入都会抛出消息为r must be a non-negative integer.的错误。二、输出顺序基于位置的字典序combinations的输出不随机、不排序值而是严格按照元素在输入数组中的位置生成字典序组合。看下面的官方示例import { combinations } from es-toolkit/array; // 从 4 个字母中选出 2 个。 combinations([A, B, C, D], 2); // Returns: [[A,B], [A,C], [A,D], [B,C], [B,D], [C,D]] // 从 4 个数字中选出 3 个。 combinations([0, 1, 2, 3], 3); // Returns: [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]观察第一个示例首元素依次固定为A与后三个组合、B与后两个组合、C与最后一个组合这正是「下标递增」的字典序枚举。测试 src/array/combinations.spec.ts 对上述两个用例做了完整断言。2.1 按位置区分元素重复值会被重复输出一个容易踩坑的语义点是元素按位置区分而非按值区分。即使输入数组中出现相同的值它们也被视为不同的元素因此可能产生「看起来相同」的多组组合import { combinations } from es-toolkit/array; combinations([1, 1, 2], 2); // Returns: [[1, 1], [1, 2], [1, 2]]结果中的两个[1, 2]分别对应「下标 0 的 1」与「下标 1 的 1」。该行为在源码注释 src/array/combinations.ts 中明确说明并由 src/array/combinations.spec.ts 专门验证。三、边界条件r 0 与 r n文档明确给出了两个关键边界行为import { combinations } from es-toolkit/array; combinations([1, 2, 3], 0); // [[]] combinations([1, 2], 5); // []r 0返回[[]]——包含一个空组合的数组数学上C(n, 0) 1即「什么都不选」这一种选择r n返回[]——空数组无法从n个元素中取出比总数还多的元素。这两个分支在源码中位于校验之后、主循环之前src/array/combinations.ts属于快速返回的优化路径const n arr.length; if (r n) { return []; } if (r 0) { return [[]]; }测试还额外覆盖了两个特殊组合场景src/array/combinations.spec.tscombinations([], 0)返回[[]]空数组上取 0 个元素仍是「空组合」combinations([], 1)返回[]combinations([1, 2, 3], 3)返回[[1, 2, 3]]——r等于数组长度时结果只有完整数组本身被包裹一次combinations([a, b, c], 1)返回[[a], [b], [c]]——r 1时退化为每个元素单独成组。四、源码级原理下标递增算法combinations的核心实现采用经典的「组合下标迭代」算法src/array/combinations.ts整个过程不使用递归而是维护一个长度固定为r的下标数组indicesconst indices Array(r); for (let i 0; i r; i) { indices[i] i; } const result: T[][] []; while (true) { const tuple: T[] Array(r); for (let i 0; i r; i) { tuple[i] arr[indices[i]]; } result.push(tuple); let i r - 1; while (i 0 indices[i] i n - r) { i--; } if (i 0) { return result; } indices[i]; for (let j i 1; j r; j) { indices[j] indices[j - 1] 1; } }其工作流程可以拆解为三步初始化indices初始为[0, 1, ..., r-1]对应第一个组合取数组最前面的r个元素。产出组合每轮循环按当前indices从arr中取值生成一个元组tuple并推入结果。推进到下一个组合从最右端开始寻找第一个「尚未到达最大位置」的下标——位置i处的下标最大值为i n - r保证后续下标还能递增排满若找到则将其1并把其右侧所有下标重置为「前一个下标 1」的连续序列。若所有下标都已到达最大值i 0说明枚举完毕返回结果。以combinations([A,B,C,D], 2)为例indices的演进序列为[0,1] → [0,2] → [0,3] → [1,2] → [1,3] → [2,3]与文档给出的 6 个结果一一对应。该算法的时间复杂度为O(C(n, r) × r)每个组合需要r次取值与复位操作且不产生额外递归栈在n较大时比递归回溯实现更节省调用栈资源。五、在 es-toolkit 中的导入方式与函数式变体5.1 导入路径combinations已通过 src/array/index.ts 统一导出推荐按需导入import { combinations } from es-toolkit/array;这样只打包该函数配合 es-toolkit 按函数拆分的构建方式可以最大程度缩小最终 bundle 体积这也是 es-toolkit 对比 lodash 的核心优势之一整体体积更小、按需引入更彻底。5.2 FP 函数式变体combinations(size)在函数式模块中还提供了柯里化版本src/fp/array/combinations.ts先传入组合长度size返回一个「接收数组并输出组合」的函数便于与pipe等函数式工具链组合使用import { combinations, pipe } from es-toolkit/fp; pipe([A, B, C], combinations(2)); // [[A, B], [A, C], [B, C]]从源码看FP 变体只是对普通版本做了参数顺序调整的薄封装src/fp/array/combinations.tsexport function combinationsT(size: number): (array: readonly T[]) T[][] { return function (array: readonly T[]): T[][] { return combinationsToolkit(array, size); }; }其抛错行为与普通版本完全一致当size不是非负整数时同样抛出错误。六、在线体验与典型应用场景官方文档为每个函数都提供了可交互的 Sandpack 示例combinations的试用代码如下对应 docs/ja/reference/array/combinations.md 中的「使用例」import { combinations } from es-toolkit/array; console.log(combinations([A, B, C, D], 2)); // 输出[[A,B], [A,C], [A,D], [B,C], [B,D], [C,D]]6.1 实战场景建议combinations适用于一切「无序选取子集」的枚举问题典型场景包括抽奖与抽样从候选人中枚举所有两人/三人小组例如combinations([Alice, Bob, Carol], 2)生成所有结对组合特征组合机器学习中枚举特征的两两/多两组合以构造交叉特征游戏与棋盘枚举落子点位、牌型组合等所有可能的选取方案测试矩阵为多个参数各取若干个取值时枚举任意r个参数的全组合用于覆盖性测试。需要留意的是组合数与输入规模呈阶乘级增长C(n, r)当n较大时结果数组会非常庞大请结合r与n的实际量级评估内存占用。6.2 与 cartesianProduct 的区分es-toolkit 中还提供了笛卡尔积函数 cartesianProduct它从多个数组中各取一个元素生成元组且元组内部顺序有意义如cartesianProduct([1, 2], [a, b])生成[[1,a], [1,b], [2,a], [2,b]]而combinations是从同一个数组中做无序选取。实际使用时请根据「是否跨数组、是否关心顺序」选择合适的工具。七、小结功能combinations(arr, r)返回所有长度为r的无序组合输出按输入数组中的位置呈字典序边界r 0返回[[]]r n返回[]r非非负整数时抛错重复值语义元素按位置区分输入含重复值时会输出「看起来相同」的组合实现下标迭代算法非递归见 src/array/combinations.ts相关行为均有 src/array/combinations.spec.ts 的测试覆盖变体FP 版combinations(size)支持柯里化与pipe组合src/fp/array/combinations.ts。如果你需要枚举无序子集combinations是 es-toolkit 中开箱即用、语义清晰且实现高效的答案。【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考