简体   繁体   English

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

[英]How to implement LoDash's sampleSize with Ramda in a functional JS way?

I'm trying to implement the LoDash's sampleSize method with Ramda in a functional way . 我正在尝试以功能性的方式通过Ramda实现LoDash的sampleSize方法。

Any ideas? 有任何想法吗? I'm totally stuck at going any further than getting a randomIndex from the given array. randomIndex从给定数组中获取randomIndex之外,我完全randomIndex任何randomIndex How do I loop through with recursion using Ramda? 如何使用Ramda遍历递归?

So, the function would look like this: 因此,该函数将如下所示:

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))

I would probably not use Ramda to do this. 我可能不会使用Ramda来做到这一点。 Note that I'm one of the founders of Ramda and a big fan. 请注意,我是Ramda的创始人之一,并且是忠实粉丝。 But Ramda is designed for functional programming. 但是Ramda是为函数式编程而设计的。 One of the chief tenets of functional programming is to use pure functions, ones which use no inputs outside their arguments and have no effects except to return a value. 函数式编程的主要宗旨之一是使用纯函数,纯函数在其参数外不使用任何输入,并且除了返回值外没有其他作用。 For the same input, they should then always return the same output. 对于相同的输入,它们应该始终返回相同的输出。 This will not work when the code is supposed to do something randomly. 当代码应该随机执行某项操作时,这将不起作用。 1 1

You could use code like what lodash does for this, an early-return version of the Fisher-Yates shuffle or you could use something like this, which also keeps its results in the order found in the original array: 您可以使用类似lodash的代码,早期返回的Fisher-Yates混编版本,也可以使用类似的代码,这样也可以使结果保持原始数组中的顺序:

 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])) 

This was written in a hurry, and may have bugs, but it should be pretty close. 这是急着写的,可能有错误,但是应该很接近。 The idea is to check each element one at a time to see if it should be included, adjusting the chance according to how many are left to include and how many are left in the list. 想法是一次检查每个元素以查看是否应将其包括在内,并根据剩余要包含的元素和列表中剩余的元素来调整机会。

A Fisher-Yates version would be more efficient than this, especially as this uses recursion which might even today not be efficiently optimized by engines, even if the specification has called for it for several years now. Fisher-Yates版本将比此版本更有效,尤其是因为它使用了递归,即使该规范已要求使用它多年,如今仍可能无法通过引擎对其进行优化。 But Fisher-Yates does not keep the original sort order. 但是Fisher-Yates不会保留原始排序顺序。 If you want that, this one might be for you. 如果您想要的话,这可能适合您。


1 Note that at one point, Ramda did have a random number extension , but that has long been discarded. 1请注意,Ramda确实确实有一个随机数扩展名 ,但长期以来一直被丢弃。 It used a repeatable pseudo-random number generator, which sounds almost like an oxymoron, but makes sense when working with pure functions. 它使用了可重复的伪随机数生成器,听起来似乎很矛盾,但是在使用纯函数时才有意义。

First let's define a function that will return a random number between min (inclusive) and max (exclusive). 首先让我们定义一个函数,该函数将返回一个介于min (含)和max (不含)之间的随机数。 We can curry it because min will always be set to 0, while max will always be set to the new list's length - 1 我们可以咖喱它,因为min总是设置为0,而max总是设置为新列表的长度-1

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

Then we need a function that will take a list and returns two things (in an array): 然后,我们需要一个函数,该函数将获取一个列表并返回两件事(在数组中):

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

Putting everything together: 将所有内容放在一起:

As you can see if the requested sample size is bigger than the actual list size, it will return the entire list but in random order. 如您所见,如果请求的样本大小大于实际列表大小,它将以随机顺序返回整个列表。

 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> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM