0

How can I get the following effect?

On the input of the number 5 and the following array:

const list = ['a', 'b', 'c'];

It should produce the following output:

['a', 'b', 'c', 'a', 'b'] // Total 5

I tried for and while, but I don't know how to restart the loop when the loop exceeds the length of the list.

Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
ahbullpay
  • 31
  • 2

3 Answers3

3

Figure out how many times to repeat the entire list in order to have enough elements; do that repetition using Array.fill and .flat; then truncate using .slice.

function repeatToLength(source, size) {
    const iterations = Math.ceil(size / source.length);
    return Array(iterations).fill(source).flat().slice(0, size);
}

console.log(repeatToLength(['a', 'b', 'c'], 5));
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
2

You can do this by repeatedly adding items from your starting list (baseList below) until you reach the desired length.

You can get the correct index in the base list by using the modulo operator (%) to get the remainder of division from dividing your output list's length by the base list's length, for example:

outputLength % baseLength == indexIntoBaseList
0 % 3 == 0
1 % 3 == 1
2 % 3 == 2
3 % 3 == 0
4 % 3 == 1
5 % 3 == 2

This gives you a repeating pattern of indices into your base list that you can use to add the correct item.

function RepeatListToLength(baseList, maxLength) {
    var output = [];
    while(output.length < maxLength) {
        output.push(baseList[output.length % baseList.length]);
    }
    return output;
}

console.log(RepeatListToLength([1,2,3,4,5], 13));
Nick is tired
  • 6,860
  • 20
  • 39
  • 51
  • Your function could just be `return [...Array(maxLength)].map((_, index) => baseList[index % baseList.length]);`. That'd likely be somewhat faster avoiding a lot of the intermediate output arrays. – Henry Ecker May 06 '23 at 04:19
  • @HenryEcker It would have to be `[...Array(maxLength)].map`; otherwise, you would just get an array of empty slots. – Unmitigated May 06 '23 at 04:37
  • @HenryEcker Yes, although I tend to cater my answers towards perceived user experience :-) – Nick is tired May 06 '23 at 14:14
1

Here's a recursive variant. The repeatToLength function calls itself with a new array consisting of two old arrays until it reaches enough length and then slices the final array to the exact length required:

const repeatToLength = (arr, len) => arr.length < len ?
  repeatToLength([...arr, ...arr], len) : arr.slice(0, len);

console.log(repeatToLength(['a', 'b', 'c'], 5));

My second solution

After doing some speed test between my recursive solution and Karls I though I give another go and write a version that creates a big empty array and then maps it using the original array and the modulus operator. (I stole the modulus idea from Nicks answer.)

It was marginally faster than the recursive solution in Chrome, and a bit faster in Firefox (still not beating Karls solution in speed in Firefox):

const repeatToLength = (arr, len) => [...new Array(len)]
  .map((x, i) => arr[i % arr.length]);


console.log(repeatToLength(['a', 'b', 'c'], 5));

Speed comparison: My two algorithms and Karls

Mine seems to be quite a bit faster in Chrome, but Karls is slightly faster in Firefox (MacOS - latest browser versions, May 2023).

Warning: This speed comparison test may take a few seconds to run depending on your cpu speed and browser.

const thomas = (arr, len) => arr.length < len ?
  thomas([...arr, ...arr], len) : arr.slice(0, len);

function karl(source, size) {
  const iterations = Math.ceil(size / source.length);
  return Array(iterations).fill(source).flat().slice(0, size);
}

const thomas2 = (arr, len) => [...new Array(len)]
  .map((x, i) => arr[i % arr.length]);

function time(func, args, runNumberOfTimes) {
  let start = performance.now();
  for (let i = 0; i < runNumberOfTimes; i++) {
    func(...args);
  }
  return Math.round(performance.now() - start) + ' ms';
}

let args;

console.log('Small array big repeat, run 2,000 times each');
args = [[1, 2, 3, 4, 5], 10000];
console.log('Array size: 5, make an array 10,000 items long.');
console.log('Karls solution', time(karl, args, 2000));
console.log('Thomas solution', time(thomas, args, 2000));
console.log('Thomas2 solution', time(thomas2, args, 2000));

console.log('\nBig array, smaller repeat, run 100 times each');
console.log('Array size: 10,000, make an array 100,000 items long');
args = ['x'.repeat(10000).split(''), 100000];
console.log('Karls solution', time(karl, args, 100));
console.log('Thomas solution', time(thomas, args, 100));
console.log('Thomas2 solution', time(thomas2, args, 100));
Nick is tired
  • 6,860
  • 20
  • 39
  • 51
Thomas Frank
  • 1,404
  • 4
  • 10
  • This is a very concise solution which favours short code length, but I think Karl's solution is much more efficient. That is, speaking from experience with other languages; I don't imagine JS interpreters/JITs would optimize this. – Andreas is moving to Codidact May 06 '23 at 05:28
  • Let's find out. :) I can performance test. Normally you get a penalty from just using recursion, since a call stack gets involved, but depends on how much recursion... Here we are doubling the length for each call. I can compare speed with Carls for some different array lengths – Thomas Frank May 06 '23 at 05:30
  • That would be nice, but I actually didn't consider the recursion being a problem at all. Tail call optimization can usually mitigate some of the issues with recursive functions. It's about the way you build the array. Karl's solution specifies the size upfront, and then fills the array (might be flattened on the second call, providing even better performance). Your solution constantly expands the array, requiring multiple resizes. – Andreas is moving to Codidact May 06 '23 at 05:32
  • We'll see. Give me a few minutes. – Thomas Frank May 06 '23 at 05:34
  • It's a really minor issue, though. Unless you're repeating the input array very many times, or the input array itself is very large, and the repeat count is more than 3, the performance differences are completely negligible. :) – Andreas is moving to Codidact May 06 '23 at 05:35
  • Added a speed comparison test to my answer - my algorithm seems to be quite a bit faster (Chrome, MacOS). Interesting. But Karls is slightly faster on the small array and quite a bit faster on the big one in Firefox. And both are much faster in Firefox than in Chrome – Thomas Frank May 06 '23 at 06:24