Возьмите X лучших предметов в круговом малине из нескольких массивов с Рамдой - PullRequest
0 голосов
/ 18 октября 2018

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

Ответы [ 4 ]

0 голосов
/ 19 октября 2018

Чтобы продемонстрировать то, что вы, возможно, видели в качестве реализации на других языках, можно использовать аппликативный экземпляр для 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>
0 голосов
/ 18 октября 2018

Здесь Рамда transpose может быть вашей базой.Добавьте ложку 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 голосов
/ 18 октября 2018

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

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 ])
0 голосов
/ 18 октября 2018

Не уверен, какие функции 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));
...