使用 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>