Возьмите X лучших предметов в круговом малине из нескольких массивов с Рамдой

1

У меня есть массив массивов и вы хотите написать функцию, которая возвращает верхнее число x элементов, беря элементы из каждого массива в порядке.

Вот пример того, что мне нужно:

    const input = [
      ["1a", "2a", "3a", "4a", "5a"],
      ["1b", "2b", "3b", "4b", "5b"],
      ["1c", "2c", "3c", "4c", "5c"],
      ["1d", "2d", "3d", "4d", "5d"]
    ];

    const takeRoundRobin = count => arr => {
      // implementation here
    };

    const actual = takeRoundRobin(5)(input);

    const expected = [
      "1a", "1b", "1c", "1d", "2a"
    ];

Я видел предложение к вопросу Scala, который решил это с помощью zip но в Ramda вы можете передавать только 2 списка в zip.

Теги:
arrays
functional-programming
ramda.js

4 ответа

3
Лучший ответ

Здесь, transpose Ramda может быть вашей базой. Добавьте unnest, тире take, и вы получите следующее:

const {take, unnest, transpose} = R

const takeRoundRobin = (n) => (vals) => take(n, unnest(transpose(vals)))

const input = [
  ['1a', '2a', '3a', '4a', '5a'],
  ['1b', '2b', '3b', '4b', '5b'],
  ['1c', '2c', '3c', '4c', '5c'],
  ['1d', '2d', '3d', '4d', '5d']
]

console.log(takeRoundRobin(5)(input))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>

Заметим также, что это может обрабатывать массивы различной длины:


Если вы хотите, чтобы иметь возможность обернуться к началу и продолжить принимать значения, вы можете заменить take с помощью recursiveTake следующим образом:

const {take, unnest, transpose, concat } = R

//recursive take
const recursiveTake = (n) => (vals) => {
  const recur = (n,vals,result) =>
    (n<=0)
      ? result
      : recur(n-vals.length,vals,result.concat(take(n,vals)))
  return recur(n,vals,[]);
};

const takeRoundRobin = (n) => (vals) => 
  recursiveTake(n)(unnest(transpose(vals)));

const input = [
  ['1a', '2a', '3a', '4a'],
  ['1b'],
  ['1c', '2c', '3c', '4c', '5c'],
  ['1d', '2d']
]

console.log(takeRoundRobin(14)(input))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>

Другая версия этой функции без явной рекурсии будет выглядеть так:

const takeCyclic = (n) => (vals) => take(
  n,
  unnest(times(always(vals), Math.ceil(n / (vals.length || 1))))
)
  • 0
    Я получил ответ, похожий на ваш (я использовал flatten вместо unnest), но мне не хватало округлости ( HMR-ответ берет 22 из структуры 20). Как бы вы сделали это в Рамде?
  • 1
    @OriDrori Я обновил ответ Скотта (2-й пример), который будет рекурсивно генерировать вывод.
Показать ещё 5 комментариев
1

Чтобы продемонстрировать, что вы видели в качестве реализации на других языках, аппликативный экземпляр для ZipList можно использовать для переноса массива, где ZipList применяет функции, содержащиеся в ZipList по- ZipList с соответствующим ZipList значений в отличие от стандартной перестановочной версии ap для списков.

const ZipList = xs => ({
  getZipList: xs,
  map: f => ZipList(R.map(f, xs)),
  ap: other => ZipList(R.zipWith(R.applyTo, other.getZipList, xs))
})

ZipList.of = x => ZipList(new Proxy([], {
  get: (target, prop) =>
    prop == 'length' ? Infinity : /\d+/.test(prop) ? x : target[prop]
}))

Это интересное требование, которое является несколькими неуклюжим, чтобы представить в JS, где of функции, чтобы произвести "чистое" значение необходимо для получения ZipList, содержащей повторяющийся список "чистых" ценности, реализованную здесь, используя Proxy экземпляр массива,

Транспонирование может быть сформировано посредством:

xs => R.unnest(R.traverse(ZipList.of, ZipList, xs).getZipList)

После всего этого мы только что изобрели R.transpose в соответствии с ответом @scott-sauyet.

Это, тем не менее, интересная реализация, о которой нужно знать.

(полный пример ниже)

const ZipList = xs => ({
  getZipList: xs,
  map: f => ZipList(R.map(f, xs)),
  ap: other => ZipList(R.zipWith(R.applyTo, other.getZipList, xs))
})

ZipList.of = x => ZipList(new Proxy([], {
  get: (target, prop) =>
    prop == 'length' ? Infinity : /\d+/.test(prop) ? x : target[prop]
}))

const fn = xs => R.unnest(R.traverse(ZipList.of, ZipList, xs).getZipList)

const input = [
  ["1a", "2a", "3a", "4a", "5a"],
  ["1b", "2b", "3b", "4b", "5b"],
  ["1c", "2c", "3c", "4c", "5c"],
  ["1d", "2d", "3d", "4d", "5d"]
];

const expected = [
  "1a", "1b", "1c", "1d", "2a"
];

const actual = R.take(5, fn(input))

console.log(R.equals(expected, actual))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
1

Здесь один из способов сделать это с помощью рекурсии -

const None =
  Symbol ()

const roundRobin = ([ a = None, ...rest ]) =>
  // base: no 'a'
  a === None
    ? []
  // inductive: some 'a'
  : isEmpty (a)
    ? roundRobin (rest)
  // inductive: some non-empty 'a'
  : [ head (a), ...roundRobin ([ ...rest, tail (a) ]) ]  

Он работает в самых разных случаях -

const data =
  [ [ 1 , 4 , 7 , 9 ]
  , [ 2 , 5 ]
  , [ 3 , 6 , 8 , 10 , 11 , 12 ]
  ]

console.log (roundRobin (data))
// => [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 ]

console.log (roundRobin ([ [ 1 , 2 , 3 ] ]))
// => [ 1 , 2 , 3 ]

console.log (roundRobin ([]))
// => []

Свободные переменные определяются с использованием префиксной нотации, более знакомой с функциональным стилем -

const isEmpty = xs =>
  xs.length === 0

const head = xs => 
  xs [0]

const tail = xs =>
  xs .slice (1)

Убедитесь, что он работает в вашем браузере ниже -

const None =
  Symbol ()
  
const roundRobin = ([ a = None, ...rest ]) =>
  a === None
    ? []
  : isEmpty (a)
    ? roundRobin (rest)
  : [ head (a), ...roundRobin ([ ...rest, tail (a) ]) ]  

const isEmpty = xs =>
  xs.length === 0
  
const head = xs => 
  xs [0]
  
const tail = xs =>
  xs .slice (1)

const data =
  [ [ 1 , 4 , 7 , 9 ]
  , [ 2 , 5 ]
  , [ 3 , 6 , 8 , 10 , 11 , 12 ]
  ]
                   
console.log (roundRobin (data))
// => [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 ]

console.log (roundRobin ([ [ 1 , 2 , 3 ] ]))
// => [ 1 , 2 , 3 ]

console.log (roundRobin ([]))
// => []

Здесь другой способ использования вторичного параметра с назначением по умолчанию -

const roundRobin = ([ a = None, ...rest ], acc = []) =>
  // no 'a'
  a === None
    ? acc
  // some 'a'
  : isEmpty (a)
    ? roundRobin (rest, acc)
  // some non-empty 'a'
  : roundRobin
      ( append (rest, tail (a))
      , append (acc, head (a))
      )

const append = (xs, x) =>
  xs .concat ([ x ])
1

Не знаете, какие функции Ramda использовать для решения этой конкретной проблемы, но вот ответ, который не использует Ramda, будет работать только в том случае, если все массивы имеют одинаковую длину:

const input = [
  ['1a', '2a', '3a', '4a', '5a'],
  ['1b', '2b', '3b', '4b', '5b'],
  ['1c', '2c', '3c', '4c', '5c'],
  ['1d', '2d', '3d', '4d', '5d'],
];

const takeRoundRobin = (count) => (arr) => {
  const recur = (arr, current, count, result) =>
    (current === count)
      ? result 
      : recur(
        arr,
        current + 1,
        count,
        result.concat(
          arr
            [current % arr.length]//x value
            [//y value
              Math.floor(current / arr.length) %
                (arr.length + 1)
            ],
        ),
      );
  return recur(arr, 0, count, []);
};

console.log(takeRoundRobin(22)(input));
  • 0
    Я не уверен, что "согласно Рамде" означает что-либо. Ramda (отказ от ответственности: я - автор Ramda) - это просто библиотека, которая предлагает набор полезных функций FP.
  • 0
    @ScottSauyet Я имел в виду, что я не знаю, какие функции от Ramda будут полезны (обновил мой ответ). Изменил ваш ответ на вопрос OP (надеюсь, вы не возражаете).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню