“重复n次”的成语是什么?

96

以下是一种在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

有几个答案建议修改内置类,但我认为这完全不可接受。


1
可能是[重复字符N次]的重复问题(https://dev59.com/VXI-5IYBdhLWcg3wZ3YR)。那个问题比你的要求略高一些,但概念(创建一个n元素的数组)是相同的。最终,只需使用`Array(n + 1)`即可。 - Chris Laplante
1
@SimpleCoder:在我的问题陈述中,我明确拒绝了[1,1,1].map(Math.random),因为它是浪费的。那么Array(3).map(Math.random)有什么不同呢?当然,我知道对于n=3,浪费是微不足道的,但对于更大的n则不是这样。 - kjo
2
@kjo:显而易见的区别是你不必显式地写出每个元素。Array(100)[1, 2, 3, 4, 5, 6, 7, ... 100]要简洁得多。至于你所说的“浪费”,我不明白问题在哪里。你认为有一种神奇的方法可以做到你想要的,而又不需要分配一个数组吗? - Chris Laplante
3
@SimpleCoder:David Brown 完全理解了这个问题... - kjo
3
我不会完全称之为习语,或许更像是一个令人感到厌恶的代码:var n = 5; alert(String(Array(n+1)).split('').map(Math.random));。 现在请原谅我,我需要用 JSON 清洗我的口腔。 - sgbj
显示剩余9条评论
7个回答

157

可以使用 Array.prototype.map 进行操作,但数组不能为空。先填充它:

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

解释:

new Array(3) 构造函数创建一个稀疏数组(或者 V8 团队称之为 "holey" 数组),它有三个空洞并且长度为三。这意味着它等同于 [,,,],它创建了 [<empty>, <empty>, <empty>,](请注意 JavaScript 的 尾随逗号)。需要注意的是,一个空槽,即一个空洞不同于被赋值的 undefinedundefined 是一个实际的值,而 <empty> 只是数组中的一个间隙。

Array.prototype.map会对数组中的每个元素调用一次。但是,由于空数组没有分配值,回调根本不会被调用。例如,[1,,2].map(v=>v*2)会给出[2,,4];中间的插槽被跳过,因为它有一个间隙。

输入Array.prototype.fill(value, start?, end?):只有一个参数,它会用指定的值填充数组中的每个插槽。从技术上讲,第一个参数是不可选的,但是通过省略它,使用undefined作为值。这没关系,因为值根本没有被使用。这样,Array(3).fill()就给了我们[undefined, undefined, undefined]

现在数组中有值了,就可以像上面看到的那样进行映射。


你可以在映射之前将空的array展开为undefined的值,也可以spread

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

解释:

ECMAScript2015或更新版本引入了数组运算符,将数组中的空洞视为undefinedArray.prototype.map是在ES5中引入的(即ES2015之前的版本),令人困惑的是,数组中的空洞被跳过,这在JS数组函数中创建了一些不一致性,具体取决于它们发布的ECMAScript版本。

展开运算符...在ES2015中引入,因此根据规范,它将给定数组中的任何空洞转换为undefined的值。换句话说,[...Array(3)]给我们[undefined, undefined, undefined],就像上面的Array(3).fill()一样。


有时候您可能需要按顺序生成数字。正如Kevin Danikowski所指出的那样,Array.prototype.map可以直接实现这一点,因为第二个参数是当前的键值:

const Fibonacci = n => Math.round(((5**.5 + 1) / 2)**n / 5**.5);

console.log(
    Array(10).fill().map((_, i) => Fibonacci(++i))
);


4
做得很好,不需要添加 npm 包。此外,如果你想返回更复杂的内容,在你的 map 函数中使用 .map((_,index)=>returnItem) - Kevin Danikowski
1
喜欢这个!在我看来,比被接受的答案更好。 要生成整数,请使用类似以下的代码: const getRandomIntArray = (max) => ( [...Array(3)].map( Math.floor(Math.random() * Math.floor(max)) ) ); - theUtherSide

39

Underscore.js有一个times函数,它可以精确地实现您想要的功能:

_.times(3, Math.random)

如果您不想使用Underscore,可以编写自己的 times 函数(从Underscore源代码中复制并略微简化):

times = function(n, iterator) {
  var accum = Array(Math.max(0, n));
  for (var i = 0; i < n; i++) accum[i] = iterator.call();
  return accum;
};

8
因为有更多当前的、赞数更少但更好的答案,所以被贬低评分。 - quinn

26

ES6最简洁优雅的语法:

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

哦,那不是为了创建数组,但仍然很棒!

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

或者 Ruby 风格:

Object.assign(Number.prototype, { times(f) { x = this; while(x-- > 0) f(); }})
3..times(() => print('wow'))

16
我想指出,n-->0不是某种箭头运算符,而是n-- > 0。虽然这很酷。 - Noumenon
2
如果您可以假设n是一个整数,那么您可以删除>0的检查,因为当n变为零时,它将等于false。然而,非整数的n将导致无限循环。 - Niet the Dark Absol
@Niet the Dark Absol 谢谢!所以不是“shortmost”,而是“safemost” ;) - Anona112
2
读完这个答案,一行都没看懂,感觉自己好蠢啊 :( - koolaang
3
在第一个例子中,他们正在创建一个函数,该函数接受两个参数“n”和“f”,其中“n”是一个整数,“f”是一个函数,并调用“f”“n”次。(它是一个ES6箭头函数)在第二个例子中,他们正在使用“Object.assign”将新方法“times”添加到“Number.prototype”中,所有JavaScript数字都继承了这些方法。但许多人认为这是不好的做法 :) - Ted Yavuzkurt
整洁的解决方案 :) - Alberto S.

25

或许可以利用Array.from的回调函数:

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

console.log(result);

使用map略有优势,因为map已经需要一个包含所有条目的数组(可能是通过fill或扩展语法创建的),然后从中创建最终数组。因此,总体而言,map解决方案将两次创建n个条目。Array.from不需要条目数组,只需具有length属性的对象即可,而Array(3)提供了这个属性。

所以根据您的喜好,也可以像这样完成上述操作:

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

console.log(result);

最后,如果您想为此创建一个repeat函数,可以将参数命名为length,并使用ES6对象字面量的简短表示法:

最后,如果您要为此创建一个repeat函数,可以将参数命名为length,并使用ES6对象字面量的简写表示法:

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

const result = repeat(3, Math.random);
console.log(result);


1
是的,它的工作方式就像对最终数组使用.map一样,但根据我的经验,只有少数开发人员能够在一瞥之间理解和使用它。但它非常简洁,尽管对于没有经验的开发人员来说可能不易读懂。 - wopolow

7
一种现代创建repeat函数的方法:
repeat = (n, cb) => {[...Array(n)].forEach(cb)}

你可以使用以下代码:

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

将输出:

0.6324464592887568
0.5969209806782131
0.7362755801487572

6
我喜欢这种方式:

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

2
这只回答了问题的一半。如何使用它来执行任何5次操作,而不仅仅是生成一个连续的数字。 - trincot
为什么要将keys数组展开到一个新数组中? Array(5).keys().map(...)就足够了,从而避免额外的数组创建。 - Lawrence Dol

0
你可以这样做:
Iet res = 'n'.repeat(3).split('').map(Math.random)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接