简体   繁体   中英

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 .

Any ideas? I'm totally stuck at going any further than getting a randomIndex from the given array. How do I loop through with recursion using 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. Note that I'm one of the founders of Ramda and a big fan. But Ramda is designed for functional programming. 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

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:

 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. But Fisher-Yates does not keep the original sort order. 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. 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). 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

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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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