开发工具【免费下载链接】remedaA utility library for JavaScript and TypeScript.项目地址https://gitcode.com/gh_mirrors/re/remeda点击查看免费下载本文以 Remeda 官方迁移映射文档 packages/docs/src/content/mapping/lodash/sample.md 为核心骨架系统讲解将 Lodash 的sample迁移到 Remeda 时遇到的四个 API 差异、三种等价替换写法以及原生一行实现并深入 sample 源码 剖析其运行时行为、复杂度设计与类型推断帮助你写出类型安全、行为等价且性能合适的随机采样代码。Lodash 的sample(collection)是一个零参数直觉的 API——传入数组或对象返回其中一个随机元素。而 Remeda 的sample是通用的子集采样工具参数与返回值语义完全不同它要求一个必填的采样大小参数并始终返回数组。本文围绕这一核心差异展开给出从 Lodash 平滑迁移到 Remeda 的完整方案。迁移时必须知道的四个 API 差异1. 必填的采样大小参数Lodash 的sample(DATA)隐式取 1 个随机元素Remeda 的sample没有默认值采样大小sampleSize是必填的第二参数。若想模拟 Lodash 的取一个随机元素必须显式传1// Lodash sample(DATA); // Remeda sample(DATA, 1 /* size */);这一设计在 sample.ts 的签名注释 中有明确体现sample(array, sampleSize)与柯里化形式sample(sampleSize)(array)两种签名都要求sampleSize作为强类型参数N extends number它同时驱动运行时行为与类型推断。2. 返回值是数组不是单值Lodash 的sample直接返回元素本身Remeda 的sample永远返回数组因为它要支持任意大小的子集采样。要提取单个值官方推荐以下三种方式// 方式一only —— 语义化返回唯一元素否则 undefined only(sample(DATA, 1 /* size */)); // 方式二原生访问器 sample(DATA, 1 /* size */)[0]; sample(DATA, 1 /* size */).at(0)!; const [result] sample(DATA, 1 /* size */);其中only是 Remeda 专为提取单元素数组设计的工具其实现只有一行判断见 only.tsconst onlyImplementation T(data: readonly T[]): T | undefined data.length 1 ? data[0] : undefined;也就是说only在数组恰好只有一个元素时返回该元素否则返回undefined天然安全。索引访问[0]与解构写法更直接但需自行处理空数组场景.at(0)!中的!即为断言写法。3. 对象采样需先调用 valuesLodash 的sample同时支持数组与对象Remeda 的sample只接受类数组容器IterableContainer。官方指引是对于对象先调用values取出值数组再做采样// Lodash sample({ a: 1, b: 2, c: 3 }); // Remeda only(sample(values({ a: 1, b: 2, c: 3 }), 1)); // 管道风格 pipe({ a: 1, b: 2, c: 3 }, values(), sample(1), only());values的底层实现直接委托给Object.values见 values.ts 第 42 行return purry(Object.values, args);其返回值类型为可枚举字符串键对应的值数组行为与原生一致。4. 热路径请考虑原生实现官方文档特别提示如果你在极高频率调用的热路径上使用sample需要极致效率时应优先选择原生实现见下文Native一节。原因是 Remeda 的sample为兼顾任意大小子集采样与类型推断做了额外工作——生成唯一索引集合、循环、排序/过滤——这些对于只取 1 个元素的场景属于超额开销。Simple三种等价替换写法速览官方给出的完整对照代码如下可直接复制使用// Lodash sample(DATA); // Remeda only(sample(DATA, 1 /* size */)); // or with a pipe pipe(DATA, sample(1 /* size */), only()); // Or with native accessors sample(DATA, 1 /* size */)[0]; sample(DATA, 1 /* size */).at(0)!; const [result] sample(DATA, 1 /* size */);三种写法各有适用场景写法风格空数组时的返回值适用场景only(sample(DATA, 1))函数式undefined类型为T \| undefined追求语义清晰、类型安全pipe(DATA, sample(1), only())管道式undefined与其他变换链式组合sample(DATA, 1)[0]/.at(0)!/ 解构命令式undefined需自行断言简单直取、无需额外 import其中管道写法依赖 Remeda 的purry机制见 purry.ts——sample(1)之所以能作为柯里化函数传入pipe正是因为sample通过purry同时生成了>// Lodash sample(DATA); // Native DATA[Math.floor(Math.random() * DATA.length)]!; DATA.at(Math.floor(Math.random() * DATA.length))!;两行写法的行为等价于等概率随机返回一个元素DATA[Math.floor(Math.random() * DATA.length)]经典下标随机访问DATA.at(Math.floor(Math.random() * DATA.length))ES2022 的Array.prototype.at支持负索引这里传入非负整数行为与下标访问一致。两点使用注意当DATA为空数组时Math.random() * 0结果为0两种写法都会访问到undefined!断言仅在非空数组时才成立Math.random()不提供加密安全随机性Remeda 的sample同样基于Math.random()涉及安全场景如抽奖防作弊需要自行替换为crypto.getRandomValues等方案。源码级解读sample 的运行时行为理解 Remedasample的行为细节有助于在迁移时做出正确判断。核心实现在 sampleImplementation其行为可归纳为边界处理0、越界与浅拷贝if (sampleSize 0) { return []; } if (sampleSize data.length) { return [...data]; }sampleSize 0直接返回空数组负值按 0 处理测试 sample.test.ts 验证了sample([1, 2, 3], -1)返回[]sampleSize data.length返回整个输入的浅拷贝[...data]而非原引用——测试用例 identity 断言了result).toStrictEqual(array)且result).not.toBe(array)见 sample.test.ts 第 67-99 行。保持输入顺序、不重复sample返回的元素保持其在输入数组中的原始顺序且不会重复。测试 doesnt reorder items 通过逐个比对元素在输入中的下标递增来验证这一点见 sample.test.ts 第 50-64 行。因此若你需要乱序的多个样本应在sample后接shuffle源码注释也明确建议If you need to get a shuffled response you can pipe the shuffle function after this onepipe(DATA, sample(3), shuffle());若你期望每次调用结果都不同sample基于Math.random()生成随机索引天然满足测试 returns a random result 通过 100 次迭代收集不同结果验证见 sample.test.ts 第 28-42 行。复杂度设计两种采样模式sample内部根据样本大小与数组长度的关系选择两种策略源码注释 sample.ts 第 182-200 行 有完整说明const actualSampleSize Math.min(sampleSize, data.length - sampleSize); const sampleIndices new Setnumber(); while (sampleIndices.size actualSampleSize) { const randomIndex Math.floor(Math.random() * data.length); sampleIndices.add(randomIndex); } if (sampleSize actualSampleSize) { // 小样本模式随机索引 排序 随机访问提取O(k log k) return [...sampleIndices] .sort((a, b) a - b) .map((index) data[index]!); } // 大样本模式全量遍历 Set.has 排除O(n) return data.filter((_, index) !sampleIndices.has(index));小样本sampleSize n/2只生成sampleSize个唯一随机索引排序后按索引提取元素。对数组主体做随机访问无需遍历整个输入复杂度约O(k log k)k为样本大小大样本sampleSize n/2改为生成要排除的索引数量同样被限制在n/2以内再对全数组做一次filter利用Set.has的O(1)判断排除复杂度O(n)。这一对称设计规避了生成大量唯一索引时的重复碰撞问题索引集合越大越容易随机到已存在的索引、循环迭代次数越多是sample高效支持任意样本大小的关键。类型层面的精确推断元组字面量保留sample的类型系统是它在迁移时的一个重要加分项。其返回值类型SampledT, Nsample.ts 第 9-21 行会根据输入类型做三种分支采样大小为0或输入为空元组时直接返回[]输入是字面量元组且采样大小是字面量数字时推导出恰好 N 个元素的全部子元组联合SampledLiteral采样大小为普通number非字面量时退化为任意长度的子元组联合SampledPrimitive。官方签名注释中的示例直观展示了这一行为sample([hello, world], 1); // [hello] // typed string[] sample([hello, world] as const, 1); // [world] // typed [hello | world]普通string[]输入得到string[]as const字面量元组输入则保留hello | world的字面量类型。类型测试 sample.test-d.ts 以 1285 行的规模覆盖了固定元组、可选元素元组、带前缀/后缀的 rest 元组、sampleSize为 0 / 等于 n / 大于 n / 字面量联合 / 原始number等各种组合。值得注意的是类型层的防退化保护测试中专门有一个针对26 元素大元组的用例sample.test-d.ts 第 878-946 行防止类型计算退化为2^|T|的指数级枚举导致 TypeScript 卡死——此前该问题已通过 pr #1355 修复。与测试相互印证的迁移结论运行时测试 sample.test.ts 对sample的全部关键行为做了断言可作为迁移时核对等价性的依据行为契约测试位置迁移启示返回元素数量等于sampleSize第 7-15 行sample(DATA, 1)恰好返回 1 个元素可用only安全提取结果是输入的子集、不凭空捏造第 17-26 行语义与 Lodash 一致从原集合中取样结果随机第 28-42 行与 Lodash 的随机语义一致无重复第 44-48 行无放回采样保持输入顺序第 50-64 行与 Lodashsample的单元素语义不冲突取多元素时如需乱序请叠加shuffle负 size 按 0、非整数四舍五入第 101-115 行sample(DATA, 1)不会触发这些边界但多元素采样时需了解选型建议何时用哪种写法综合官方文档与源码实现可按以下规则决策绝大多数业务代码类型安全优先使用only(sample(DATA, 1))或pipe(DATA, sample(1), only())享受字面量类型推断与管道组合能力简单直取、不想引入额外依赖sample(DATA, 1)[0]或const [result] sample(DATA, 1)对象采样先values再sample需要取多个随机元素且保持顺序sample(DATA, N)直接用需要乱序再叠加shuffle()热路径极致性能直接使用原生一行DATA[Math.floor(Math.random() * DATA.length)]!绕开库函数的集合运算与类型计算开销加密安全场景无论选择库函数还是原生写法Math.random()都不满足安全要求需替换为 CSPRNG。输出文章结束赞分享开发工具【免费下载链接】remedaA utility library for JavaScript and TypeScript.项目地址https://gitcode.com/gh_mirrors/re/remeda点击查看免费下载相关推荐pkg将 Node.js 项目打包为单个可执行文件——命令行实战与源码级原理指南pkg将 Node.js 项目打包为单个可执行文件——命令行实战与源码级原理指南 pkg 是用于把 Node.js 项目打包为单个可执行文件的命令行工具打包开发工具用 Podman 将容器化 Web 服务器端口映射到宿主机devops-exercises 实战指南用 Podman 将容器化 Web 服务器端口映射到宿主机devops exercises 实战指南 本指南以 devops exercises 仓库中 容器开发工具Remeda 迁移指南用 filter isNot 等价替换 Ramda 的 rejectRemeda 迁移指南用 filter isNot 等价替换 Ramda 的 reject 在将代码从 Ramda 迁移到 Remeda 时 rejec开发工具上一篇Mac Mouse Fix 安装指南3 种方式快速选对不再纠结下一篇Mac用户必看3分钟解决NTFS硬盘读写难题免费开源工具Nigate完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考