最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

javascript - Take top X total items in a round robin from multiple arrays with Ramda - Stack Overflow

matteradmin5PV0评论

I have an array of arrays and want to write a function that returns the top x number of items, by taking items from each array in order.

Here is an example of what I'm after:

    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"
    ];

I saw a suggestion to a Scala question that solved this using zip but in Ramda you can only pass 2 lists to zip.

I have an array of arrays and want to write a function that returns the top x number of items, by taking items from each array in order.

Here is an example of what I'm after:

    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"
    ];

I saw a suggestion to a Scala question that solved this using zip but in Ramda you can only pass 2 lists to zip.

Share Improve this question asked Oct 18, 2018 at 13:33 cpolivercpoliver 655 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 6

Here, Ramda's transpose can be your base. Add a dollop of unnest, a dash of take, and you get this:

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./ajax/libs/ramda/0.25.0/ramda.js"></script>

Note also that this can handle arrays of varying lengths:


If you want to be able to wrap around to the beginning and continue taking values, you could replace take with a recursiveTake like this:

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./ajax/libs/ramda/0.25.0/ramda.js"></script>

Another version of that function, without the explicit recursion would look like:

const takeCyclic = (n) => (vals) => take(
  n,
  unnest(times(always(vals), Math.ceil(n / (vals.length || 1))))
)

Here's one way you can do it using recursion –

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) ]) ]  

It works in a variety of cases –

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 ([]))
// => []

Free variables are defined using prefix notation which is more familiar with functional style –

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

const head = xs => 
  xs [0]

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

Verify it works in your browser below –

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 ([]))
// => []

Here's another way using a secondary parameter with default assignment –

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 ])

To demonstrate what you may have seen as the implementation in other languages, the 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 for lists.

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]
}))

This has an interesting requirement which is somewhat clunky to represent in JS, where the of function to produce a "pure" value needs to produce a ZipList containing a repeating list of the "pure" value, implemented here using a Proxy instance of an array.

The transpose can then be formed via:

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

After all of this, we have just reinvented R.transpose as per the answer from @scott-sauyet.

It is nevertheless an interesting implementation to be aware of.

(full example below)

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./ajax/libs/ramda/0.25.0/ramda.min.js"></script>

Not sure what Ramda functions to use to address this particular problem but here is an answer not using Ramda that'll only work if all arrays are the same length:

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));

Post a comment

comment list (0)

  1. No comments so far