У меня есть массив массивов и вы хотите написать функцию, которая возвращает верхнее число 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.
Здесь, 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))))
)
Чтобы продемонстрировать, что вы видели в качестве реализации на других языках, аппликативный экземпляр для 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>
Здесь один из способов сделать это с помощью рекурсии -
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 ])
Не знаете, какие функции 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));