首页 > 解决方案 > 如何以函数式 JS 方式用 Ramda 实现 LoDash 的 sampleSize?

问题描述

我正在尝试用 Ramda以一种功能性的方式实现 LoDash 的sampleSize方法。

有任何想法吗?randomIndex除了从给定的数组中获取 a 之外,我完全被困住了。如何使用 Ramda 进行递归循环?

所以,函数看起来像这样:

export const sampleSize = curry((size, list) => compose(
  // two paths of code
  // one to splice messages at the randomIndex
  // recursion with the spliced array until length === size
  randomIndex
)(list))

标签: javascriptfunctional-programmingramda.js

解决方案


我可能不会使用 Ramda 来做到这一点。请注意,我是 Ramda 的创始人之一,也是我的忠实粉丝。但是 Ramda 是为函数式编程而设计的。函数式编程的主要原则之一是使用纯函数,即在参数之外不使用任何输入,并且除了返回值之外没有任何效果。对于相同的输入,它们应该总是返回相同的输出。当代码应该随机执行某些操作时,这将不起作用。1

您可以使用类似 lodash 的代码,Fisher-Yates shuffle的早期返回版本,或者您可以使用类似这样的代码,这也将其结果保持在原始数组中的顺序:

const sampleSize = (size, list, collected = []) => size < 1 || list.length < 1
  ? collected
  : size >= list.length
    ? [...collected, ...list] // or throw error?
    : Math.random() < size / list.length
      ? sampleSize(size -1, list.slice(1), [...collected, list[0]])
      : sampleSize(size, list.slice(1), collected)

console.log(sampleSize(4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
console.log(sampleSize(4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
console.log(sampleSize(4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))

console.log(sampleSize(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
console.log(sampleSize(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
console.log(sampleSize(20, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))

这是匆忙写的,可能有错误,但应该很接近。这个想法是一次检查每个元素,看看它是否应该被包括在内,根据剩下的数量和列表中剩下的数量来调整机会。

Fisher-Yates 版本会比这更有效,特别是因为它使用了递归,即使在规范已经要求它好几年了,即使在今天,引擎也可能无法有效地优化它。但是 Fisher-Yates 并没有保持原来的排序顺序。如果你想要,这个可能适合你。


1请注意,在某一时刻,Ramda 确实有一个随机数扩展,但它早已被丢弃。它使用了一个可重复的伪随机数生成器,这听起来很矛盾,但在使用纯函数时是有意义的。


推荐阅读