“重复 n 次”的成语?

这里有一种在 JS 中生成3个随机数数组的方法,有点浪费而且不切实际:

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

使用虚拟数组(例如 [1, 1, 1]) ,只是为了在其上调用 map,对于足够大 N来说,既浪费(内存)又不切实际。

人们想要的是一个类似于假设的东西:

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

使用普通的 JavaScript,我们能做的最接近的事情是什么?

我知道像 Underscore 这样的库,但是这里我尽量避免使用库。

我看了 重复一个字符串多次的答案,但它不适用于一般情况。例如:

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

其他几个答案建议修改内置类; 我认为这种做法是完全不可接受的。

85347 次浏览

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

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

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

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

I like this way:

[...Array(5).keys()].forEach(index =>
console.log(`do something ${index}`
)

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

You could do the following:

Iet res = 'n'.repeat(3).split('').map(Math.random)