资讯动态

es-toolkit fp 模块 windowed 实战:在 pipe 中实现滑动窗口与惰性求值

发布时间:2026/9/16 18:25:03 来源:尧图企业网站定制
es-toolkit fp 模块 windowed 实战在 pipe 中实现滑动窗口与惰性求值【免费下载链接】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本文围绕 es-toolkit 函数式编程入口es-toolkit/fp中的windowed算子展开讲解如何用pipe(array, windowed(size, step, options))的方式对数组做滑动窗口切分并深入源码剖析其全窗口模式可在pipe内惰性求值、可提前终止的实现机制。读完本文你将掌握该算子的全部参数、边界行为与类型签名并能在时间序列、n-gram 提取等场景中写出既简洁又高效的管道式代码。windowed 是什么为 pipe 而生的滑动窗口算子windowed会从输入数组中切出若干个长度固定的窗口子数组窗口沿着数组按固定步长滑动从而形成一段连续的快照序列。这是时间序列分析如移动平均、字符串 n-gram 提取、数组模式匹配等任务的基础操作。在 es-toolkit 中该功能有两个版本普通版windowed位于es-toolkit/arraydata-first 风格直接接收数组作为第一个参数fp 版windowed本文主题data-last 风格先接收size、step、options等配置返回一个把readonly T[]映射为T[][]的函数专门与函数式组合入口pipe搭配使用import { pipe, windowed } from es-toolkit/fp; const result pipe(array, windowed(size, step, options));这种设计让windowed可以像map、filter、take一样作为一等算子嵌入管道与其他变换任意串联。若你的代码并不需要管道组合官方文档明确建议在普通代码中使用>import { pipe, windowed } from es-toolkit/fp; // 窗口大小为 2默认步长 1相邻两两成组 pipe([1, 2, 3, 4], windowed(2)); // [[1, 2], [2, 3], [3, 4]] // 窗口大小为 2步长 2不重叠切分 pipe([1, 2, 3, 4], windowed(2, 2)); // [[1, 2], [3, 4]]第一个示例中窗口每次前进 1 位产生重叠窗口第二个示例中窗口每次前进 2 位恰好与窗口等宽行为等同于按块切分。这与 src/fp/array/windowed.spec.ts 中的首个测试用例works in a pipe完全一致。参数详解fp 版windowed的函数签名为windowed(size, step?, options?)三个参数含义如下参数类型必填默认值说明sizenumber是—每个窗口的长度必须是正整数stepnumber否1相邻两个窗口起点之间前进的位置数必须是正整数optionsWindowedOptions否{}配置项目前仅包含partialWindowsoptions.partialWindowsboolean默认false决定是否把数组末尾凑不齐一个完整窗口的残缺窗口也一并返回。在 src/array/windowed.ts 中定义的WindowedOptions接口即export interface WindowedOptions { /** * Whether to include partial windows at the end of the array. * 默认只返回完整窗口末尾不足以凑成完整窗口的元素会被忽略 * 设为 true 后末尾这些更小的窗口也会被包含进结果。 */ partialWindows?: boolean; }再看带partialWindows的管道示例与 src/fp/array/windowed.spec.ts 的第三个测试一致pipe([1, 2, 3], windowed(2, 2, { partialWindows: true })); // [[1, 2], [3]] // 末尾的 [3] 是不足 2 的残缺窗口返回类型windowed(size, step?, options?)返回一个函数其类型为(array: readonly T[]) T[][]即接收一个只读数组返回由多个窗口子数组组成的二维数组。每个窗口都是原数组元素的快照底层通过slice产生新的子数组见 src/array/windowed.ts因此修改窗口内容不会影响原数组。错误处理当size或step不是正整数小于等于 0 或非整数时windowed会抛出Error。核心实现位于 src/array/windowed.tsif (size 0 || !Number.isInteger(size)) { throw new Error(Size must be a positive integer.); } if (step 0 || !Number.isInteger(step)) { throw new Error(Step must be a positive integer.); }src/array/windowed.spec.ts 的测试用例覆盖了全部非法输入size 0、step 0、负数、小数如0.5都会触发对应报错例如windowed([1, 2, 3], 0)抛出Error: Size must be a positive integer.。行为边界结合测试看窗口语义普通版windowed的单元测试src/array/windowed.spec.ts完整刻画了滑动窗口的边界行为这些语义同样适用于 fp 版步长小于窗口产生重叠窗口如windowed([1..6], 3, 2)得到[[1,2,3],[3,4,5]]步长等于窗口退化为不重叠分块如windowed([1..6], 3, 3)得到[[1,2,3],[4,5,6]]与chunk行为一致步长大于窗口会跳过部分元素如windowed([1..6], 2, 4)得到[[1,2],[5,6]]步长超过数组长度只产生第一个窗口如windowed([1..6], 2, 10)得到[[1,2]]空数组返回空数组[]窗口大于数组且开启partialWindows返回逐级缩短的残缺窗口如windowed([1,2], 5, 1, { partialWindows: true })得到[[1,2],[2]]见 docs/reference/array/windowed.md 示例。源码深潜全窗口模式如何在 pipe 中惰性求值fp 版windowed最大的亮点是只返回完整窗口默认模式时它在pipe内部具备惰性求值能力可以和相邻的map、filter、take等算子融合成单趟扫描并支持提前终止。这一能力由 src/fp/_internal/lazy.ts 提供的基础设施支撑。入口eager 与 lazy 两条路径的分流src/fp/array/windowed.ts 的入口逻辑如下const resolvedStep step ?? 1; const partialWindows options?.partialWindows ?? false; function windowedEager(array: readonly T[]): T[][] { return windowedToolkit(array, size, step, options); } if (partialWindows || !Number.isInteger(size) || size 0 || !Number.isInteger(resolvedStep) || resolvedStep 0) { return windowedEager; // 走即时计算 } const windowedLazy (emit: SinkT[]): SinkT { /* ... */ }; return combineEagerAndLazyFunctions(windowedEager, windowedLazy);可见当开启partialWindows或参数非法时直接返回 eager 版本即时计算以保证残缺窗口和错误抛出语义只有在纯全窗口 合法参数的默认场景下才附加惰性变换。eager 版本内部委托给原版实现windowedToolkit即 src/array/windowed.ts 中基于for循环与arr.slice(i, i size)的实现。惰性实现push 式流水线 环形缓冲windowedLazy是一个典型的push 式惰性变换详见 src/fp/_internal/lazy.ts 的文档注释它不是用生成器而是每个函数接收下一级 sink通过emit把值推下去pipe负责把这些变换按从后往前的顺序组合成一条 sink 链再用一个循环驱动整条链。其核心片段const buffer: T[] []; let index 0; return (value: T): boolean { buffer.push(value); if (buffer.length size) { buffer.shift(); // 维持缓冲区长度不超过 size } if (buffer.length size) { const start index - size 1; if (start % resolvedStep 0) { const shouldContinue emit(buffer.slice()); // 推一个窗口快照 index; return shouldContinue; } } index; return true; };这里用了一个长度恒定的滑动缓冲每来一个元素先入队超出size就从头部弹出再用start % resolvedStep 0判断当前窗口起点是否落在步长网格上命中才把buffer.slice()拷贝快照交给下一级。返回false表示整条管道提前结束从而避免继续遍历剩余输入。pipe 如何驱动融合与短路在 src/fp/pipe.ts 的实现中pipe会把连续的惰性算子聚合成一组chunkFunctions当输入是可迭代对象时走lazyPipe从最后一个函数开始把每个函数的lazy变换层层包裹成最终 sink然后对输入逐元素驱动一旦某级 sink 返回false如take的短路驱动循环立即break。windowed正是通过combineEagerAndLazyFunctions(windowedEager, windowedLazy)见 src/fp/_internal/lazy.ts把 eager 实现和惰性变换打包成一个带lazy元数据的算子函数返回给调用方pipe据此识别并融合它。测试如何验证惰性src/fp/array/windowed.spec.ts 用 spy 严格验证了惰性语义it(supports lazy evaluation for full windows, () { const spy vi.fn((value: number) value); expect(pipe([1, 2, 3, 4], map(spy), windowed(2), take(1))).toEqual([[1, 2]]); expect(spy).toHaveBeenCalledTimes(2); });map(spy)之后接windowed(2)再接take(1)因为只需要第一个窗口map的回调实际只被调用了2 次恰好凑出一个窗口就停止剩余元素完全没有被访问。这就是融合 提前终止的直接证据如果不走惰性路径map会先对整个数组执行完 4 次回调再交给后面的算子。使用建议与典型场景何时用 fp 版在pipe/flow中与其他算子组合时使用 fp 版普通命令式代码建议直接用原版windowed。何时会失去惰性一旦开启partialWindows: true为了保留末尾残缺窗口fp 版会自动退回 eager 即时计算路径见上文分流逻辑管道中位于其前的算子将无法与该段融合短路。因此追求极致性能时应优先考虑纯全窗口模式。典型应用结合窗口语义可以轻松实现移动平均对windowed(数据, n)的每个窗口求均值、文本 n-gram 提取对分词后的字符串数组做windowed(n)、以及各类滑窗检测算法配合pipe还可继续用map、filter、take等算子做后续加工。小结es-toolkit fp 版的windowed是一个典型的 contenteditable="false">【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价