1

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.

0

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

Comments

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

3 Comments

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.
@HenryEcker It would have to be [...Array(maxLength)].map; otherwise, you would just get an array of empty slots.
@HenryEcker Yes, although I tend to cater my answers towards perceived user experience :-)
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));

6 Comments

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.
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
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.
We'll see. Give me a few minutes.
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. :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.