如何以功能性JS方式使用Ramda实现LoDash的sampleSize?

时间:2019-01-20 06:09:41

标签: javascript functional-programming ramda.js

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

有什么想法吗?除了从给定数组中获取randomIndex之外,我完全不愿意采取任何其他措施。如何使用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))

2 个答案:

答案 0 :(得分:2)

我可能不会使用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确实有一个random number extension,但长期以来一直被丢弃。它使用了可重复的伪随机数生成器,听起来似乎很矛盾,但是在使用纯函数时才有意义。

答案 1 :(得分:1)

首先让我们定义一个函数,该函数将返回一个min(包括)和max(不包括)之间的随机数。我们可以使用它,因为min将始终设置为0, 而max将始终设置为新列表的长度-1

const random = curry((min, max) => Math.floor(Math.random() * (max - min) - min));

然后我们需要一个函数,该函数将获取一个列表并返回两件事(在数组中):

  1. 随机选择的元素
  2. 没有该元素的新列表
const takeFromList = converge(
  converge(pair, [nth, flip(remove)(1)]) [compose(random(0), length), identity]);

将所有内容放在一起:

如您所见,如果请求的样本大小大于实际列表大小,它将以随机顺序返回整个列表。

const {curry, min, nth, identity, converge, pair, flip, compose, length, remove, flatten} = R;

const random = curry((min, max) => Math.floor(Math.random() * (max - min) - min));
const takeFromList = converge(converge(pair, [nth, flip(remove)(1)]), [compose(random(0), length), identity]);

const sampleSize = curry((size, list) => {
  const [el, newList] = takeFromList(list);
  const len = min(size, list.length);
  return len - 1 > 0 ? flatten([el, sampleSize(len - 1, newList)]) : [el];
});

console.log(sampleSize(2, [1,2,3,4,5]));
console.log(sampleSize(2, [1,2,3,4,5]));
console.log(sampleSize(2, [1,2,3,4,5]));
console.log(sampleSize(20, [1,2,3,4,5]));
console.log(sampleSize(20, [1,2,3,4,5]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>