94

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.

trincot
  • 317,000
  • 35
  • 244
  • 286
kjo
  • 33,683
  • 52
  • 148
  • 265
  • 1
    possible duplicate of [Repeat Character N Times](http://stackoverflow.com/questions/1877475/repeat-character-n-times). That question is asking slightly more than yours, but the concept (creating an array of n elements) is the same. Ultimately, just use `Array(n + 1)` – Chris Laplante Aug 28 '13 at 23:29
  • Do you want integers? A random number between what and what? – StackSlave Aug 28 '13 at 23:32
  • 1
    @SimpleCoder: I explicitly rejected `[1,1,1].map(Math.random)` in my question statement for being wasteful. How is `Array(3).map(Math.random)` different? Of course, I know that for *n*=3 the waste is negligible, but not so for larger *n*. – kjo Aug 28 '13 at 23:36
  • @PHPglue: you're taking too literally what was only a simple-to-describe example. – kjo Aug 28 '13 at 23:37
  • 2
    @kjo: The obvious difference is that you don't have to write every element explicitly. `Array(100)` is a helluva lot more terse than `[1, 2, 3, 4, 5, 6, 7, ... 100]`. As for this "waste" you speak of - I don't understand the problem. Do you think there is some magic way of doing what you want, *without* allocating an array? – Chris Laplante Aug 28 '13 at 23:39
  • And loop/push is out of the question? – Yuriy Galanter Aug 28 '13 at 23:40
  • @SimpleCoder: do you ***truly believe*** that it is ***necessary*** to ***allocate an array*** in order to repeat a function call *n* times??? – kjo Aug 28 '13 at 23:47
  • @YuriyGalanter: certainly not, but it is not what I'd call an idiom... – kjo Aug 28 '13 at 23:48
  • @SimpleCoder This is definitely not a duplicate of that question. This is about calling a function multiple times, not repeating a value. – David Brown Aug 28 '13 at 23:56
  • @kjo: No, no, no - let's stop moving the goal post. Every code example you posted produces an array. If that's not what you meant, and your claim of "repeat a function call n times" is true, then I'd refer you to the `for` loop. Your question is very unclear. – Chris Laplante Aug 28 '13 at 23:57
  • 3
    @SimpleCoder: David Brown understood the question perfectly... – kjo Aug 28 '13 at 23:59
  • 3
    I wouldn't exactly call this, an idiom, maybe a disgustiom: `var n = 5; alert(String(Array(n+1)).split('').map(Math.random));`. Now please excuse me while I go wash my mouth out with JSON. – sgbj Aug 29 '13 at 00:03
  • @kjo: That's nice, but let's not distract from the point. A better way of asking your question would have been, "How to fill an array with *n* invocations of a function", or something similar. "How to repeat something n times" is not accurate to what you were apparently looking for. That being said, I'm glad you found an answer that works for you. – Chris Laplante Aug 29 '13 at 00:07
  • @SimpleCoder: look at the accepted answer. That's what I was looking for. At least someone got it. – kjo Aug 29 '13 at 00:08

7 Answers7

154

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))
);
Okku
  • 7,468
  • 4
  • 30
  • 43
  • 4
    Great job, no need for npm package added. Also, if you want to return something more complicated, in your map use `.map((_,index)=>returnItem)` – Kevin Danikowski Apr 09 '18 at 16:34
  • 1
    Love this! IMO, better than the accepted answer. To generate integers use something like: `const getRandomIntArray = (max) => ( [...Array(3)].map( Math.floor(Math.random() * Math.floor(max)) ) );` – theUtherSide Jun 05 '18 at 19:54
39

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;
};
David Brown
  • 13,336
  • 4
  • 38
  • 55
26

shortmost elegant ES6:

let times = (n, f) => { while(n-- > 0) f(); }

oh, That's not for creating an array, but it's still neat!

times(3, () => print('wow'))

or Ruby style:

Object.assign(Number.prototype, { times(f) { x = this; while(x-- > 0) f(); }})
3..times(() => print('wow'))
Advait Junnarkar
  • 3,283
  • 4
  • 13
  • 25
Anona112
  • 3,724
  • 4
  • 21
  • 30
  • 16
    I'd like to point out that `n-->0` is not some kind of arrow operator, it's `n-- > 0`. This is cool, though. – Noumenon Mar 19 '16 at 11:55
  • 2
    If you can assume `n` is an integer, you can remove the `>0` check because when `n` hits zero it will equate to false. Non-integer `n` will result in an infinite loop however. – Niet the Dark Absol Mar 20 '16 at 12:38
  • @Niet the Dark Absol thanks! so it is not 'shortmost' but 'safemost' ;) – Anona112 Mar 20 '16 at 12:44
  • 2
    I feel very stupid after reading this answer and not understanding single line of it :( – koolaang Apr 29 '16 at 16:24
  • 3
    @koolaang in the first insance, they're creating a function that takes two arguments, `n` and `f`, `n` being an integer, and `f` being a function, and calls `f` `n` times. (It is an ES6 Arrow Function) In the second instance, they're taking `Number.prototype`, which *all* JavaScript numbers inherit methods from, and adding a new method to it called `times` using `Object.assign`. Many would consider this bad practice though :) – Ted Yavuzkurt Mar 26 '17 at 01:54
  • neat solution :) – Alberto S. Feb 07 '21 at 22:30
25

Maybe the Array.from callback can be of use:

var result = Array.from(Array(3), Math.random);

console.log(result);

There is a slight advantage here over using map: map already needs an array with all the entries (maybe created with fill or the spread syntax), to then create the final array from that. So in total a map solution will create n entries twice. Array.from does not need an array with entries, just an object with a length property will do, and Array(3) is providing that.

So depending on your preferences, the above can also be done like this:

var result = Array.from({length:3}, Math.random);

console.log(result);

Finally, if you would create a repeat function for this, you can name the argument length and use the ES6 short notation for object literals:

const repeat = (length, cb) => Array.from({length}, cb);

const result = repeat(3, Math.random);
console.log(result);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Yeah it works just like `.map` on final array, tough from my experience a few developers use this and understand it at glance. But its pretty clean, disregarding readability for unexperienced devs – wopolow Jan 27 '18 at 01:33
7

A modern way to create that repeat function:

repeat = (n, cb) => {[...Array(n)].forEach(cb)}

You can then use it with:

repeat(3, _ => console.log(Math.random()))

Would output:

0.6324464592887568
0.5969209806782131
0.7362755801487572
vinyll
  • 11,017
  • 2
  • 48
  • 37
6

I like this way:

[...Array(5).keys()].forEach(index =>
  console.log(`do something ${index}`
)
Rafael Motta
  • 2,328
  • 2
  • 19
  • 18
  • 2
    This only covers half of the question. How would this be used to do anything 5 times, not just generate a sequential number. – trincot Aug 14 '18 at 08:06
  • And why spread the keys array into a new array? `Array(5).keys().map(...)` would suffice and avoid an extra array creation. – Lawrence Dol Dec 08 '21 at 20:21
0

You could do the following:

Iet res = 'n'.repeat(3).split('').map(Math.random)
Erik Martín Jordán
  • 4,332
  • 3
  • 26
  • 36