Как реализовать sampleSize LoDash с Ramda функциональным способом JS? - PullRequest
0 голосов
/ 20 января 2019

Я пытаюсь реализовать метод LoDash sampleSize с Рамдой функциональным способом .

Есть идеи? Я полностью застрял в том, чтобы пойти дальше, чем получить randomIndex из данного массива. Как мне пройти через рекурсию, используя Рамду?

Итак, функция будет выглядеть так:

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 голосов
/ 20 января 2019

Я бы, вероятно, не использовал Рамду для этого.Обратите внимание, что я один из основателей 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]))

Это было написано на скорую руку и может содержать ошибки, но должно быть довольно близко.Идея состоит в том, чтобы проверять каждый элемент по одному, чтобы увидеть, должен ли он быть включен, корректируя шанс в зависимости от того, сколько осталось включить и сколько осталось в списке.

Версия Фишера-Йейтсабудет более эффективным, чем это, особенно если использовать рекурсию, которая даже сегодня не может быть эффективно оптимизирована двигателями, даже если спецификация требует этого уже несколько лет.Но Фишер-Йейтс не сохраняет первоначальный порядок сортировки.Если вы этого хотите, этот может быть для вас.


1 Обратите внимание, что в какой-то момент Рамда имел расширение случайного числа , но этодавно отброшенОн использовал воспроизводимый генератор псевдослучайных чисел, который звучит почти как оксюморон, но имеет смысл при работе с чистыми функциями.

0 голосов
/ 20 января 2019

Сначала давайте определим функцию, которая будет возвращать случайное число от 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>
...