使用 Ramda 从多个数组中循环获取前 X 个总项目
Take top X total items in a round robin from multiple arrays with Ramda
我有一个数组数组,我想编写一个函数,通过按顺序从每个数组中取出项目,returns 最多 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。
不确定使用什么 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));
在这里,Ramda 的 transpose
can be your base. Add a dollop of unnest
, a dash of 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))))
)
这是您可以使用递归实现的一种方法 –
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 ])
为了演示您可能已经看到的其他语言的实现,applicative instance for a ZipList
can be used to transpose the array, where a ZipList
applies the functions contained in the ZipList
in a pair-wise manner with the corresponding ZipList
of values unlike the standard permutative version of 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
函数生成 "pure" 值需要生成一个 ZipList
包含重复列表"pure" 值,在此处使用数组的 Proxy
实例实现。
然后可以通过以下方式形成转置:
xs => R.unnest(R.traverse(ZipList.of, ZipList, xs).getZipList)
在所有这一切之后,我们刚刚根据 @scott-sauyet 的回答重新发明了 R.transpose
。
尽管如此,这是一个值得关注的有趣实现。
(下面的完整示例)
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>
我有一个数组数组,我想编写一个函数,通过按顺序从每个数组中取出项目,returns 最多 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。
不确定使用什么 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));
在这里,Ramda 的 transpose
can be your base. Add a dollop of unnest
, a dash of 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))))
)
这是您可以使用递归实现的一种方法 –
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 ])
为了演示您可能已经看到的其他语言的实现,applicative instance for a ZipList
can be used to transpose the array, where a ZipList
applies the functions contained in the ZipList
in a pair-wise manner with the corresponding ZipList
of values unlike the standard permutative version of 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
函数生成 "pure" 值需要生成一个 ZipList
包含重复列表"pure" 值,在此处使用数组的 Proxy
实例实现。
然后可以通过以下方式形成转置:
xs => R.unnest(R.traverse(ZipList.of, ZipList, xs).getZipList)
在所有这一切之后,我们刚刚根据 @scott-sauyet 的回答重新发明了 R.transpose
。
尽管如此,这是一个值得关注的有趣实现。
(下面的完整示例)
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>