Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiom for "repeat n times"?

Tags:

javascript

Here's a somewhat wasteful and impractical way to produce an array of 3 random numbers in JS:

[1, 1, 1].map(Math.random) // Outputs: [0.63244645928, 0.59692098067, 0.73627558014] 

The use of a dummy array (e.g. [1, 1, 1]), just so that one can call map on it, is -- for sufficiently large n -- both wasteful (of memory) and impractical.

What one would like, would be something like a hypothetical:

repeat(3, Math.random) // Outputs: [0.214259553965, 0.002260502324, 0.452618881464] 

What's the closest we can do using vanilla JavaScript?

I'm aware of libraries like Underscore, but I'm trying to avoid libraries here.

I looked at the answers to Repeat a string a number of times, but it is not applicable in general. E.g.:

Array(3).map(Math.random) // Outputs: [undefined, undefined, undefined] Array(4).join(Math.random()) // Outputs a concatenation of a repeated number Array(3).fill(Math.random()) // Fills with the same number 

Several other answers propose modifying a built-in class; a practice that I consider completely unacceptable.

like image 431
kjo Avatar asked Aug 28 '13 23:08

kjo


2 Answers

It can be done using Array.prototype.map, but the array can't be empty. Fill it first:

console.log(     Array(3).fill().map(Math.random) );

Explanation:

The new Array(3) constructor creates a sparse array (or "holey" array, as the V8 team calls them) with three holes in it and a length of three. This means that it's equivalent to [,,,], which creates [<empty>, <empty>, <empty>,] (note JavaScript's trailing commas). Note that an empty slot, i.e. a hole is not the same as undefined as an assigned value. undefined is an actual value, whereas <empty> is just a gap in the array.

Array.prototype.map is called once for each element in the array. But, because an empty array has no assigned values, the callback doesn't get called at all. For example, [1,,2].map(v=>v*2) would give [2,,4]; the middle slot is skipped, as it has a gap there.

Enter Array.prototype.fill(value, start?, end?): with only one argument, it fills every slot in the array with the specified value. Technically, the first parameter is not optional, but by omitting it, undefined is used as the value. This is okay, because the value isn't being used anyway. This way Array(3).fill() gives us [undefined, undefined, undefined].

Now that the array has values in it, it can be mapped over, like seen above.


You could also spread the empty array into values of undefined before mapping:

console.log(     [...Array(3)].map(Math.random) );

Explanation:

Array operators introduced in ECMAScript2015 or newer treat holes in arrays as undefined values. Array.prototype.map was introduced in ES5 (I.E. what preceded ES2015), where, confusingly, holes in arrays are to be skipped over, creating a little bit of inconsistency in JS Array functions depending on which edition of ECMAScript they were released in.

The spread operator ... was introduced in ES2015, so as per spec, it converts any holes in the given array into values of undefined. In other words, [...Array(3)] gives us [undefined, undefined, undefined], just like Array(3).fill() did above.


Sometimes you may need to seed in numbers sequentially. As pointed out by Kevin Danikowski, Array.prototype.map gives you that out of the box, as the second parameter is the current key:

const Fibonacci = n => Math.round(((5**.5 + 1) / 2)**n / 5**.5);  console.log(     Array(10).fill().map((_, i) => Fibonacci(++i)) );
like image 120
Okku Avatar answered Sep 25 '22 08:09

Okku


Underscore.js has a times function that does exactly what you want:

_.times(3, Math.random) 

If you don't want to use Underscore, you can just write your own times function (copied and slightly simplified from the Underscore source):

times = function(n, iterator) {   var accum = Array(Math.max(0, n));   for (var i = 0; i < n; i++) accum[i] = iterator.call();   return accum; }; 
like image 40
David Brown Avatar answered Sep 24 '22 08:09

David Brown