创建零填充 JavaScript 数组的最有效方法?

IT技术 javascript arrays
2021-01-11 20:00:14

在 JavaScript 中创建任意长度的零填充数组的最有效方法是什么?

6个回答

ES6 引入了Array.prototype.fill. 它可以像这样使用:

new Array(len).fill(0);

不确定它是否很快,但我喜欢它,因为它简短且自我描述。

它仍然不在 IE 中(检查兼容性),但是有一个polyfill 可用

请注意,要遍历数组(例如 map 或 forEach),必须设置,否则将跳过这些索引。你设置的值可以是任何你想要的——甚至是未定义的。示例:尝试new Array(5).forEach(val => console.log('hi'));new Array(5).fill(undefined).forEach(val => console.log('hi'));.
2021-03-15 20:00:14
我看到fill()是颇有几分比for循环更慢,当阵列变得非常大:jsperf.com/zero-filling-large-arrays 之间并没有显著差异new Array(n)a = []; a.length = n
2021-03-17 20:00:14
......我承认我错过了这部分......当我将第二行添加到测试中时arr.fill(0) ......一切都发生了变化。现在,new Array()在大多数情况下使用速度更快,除非数组大小大于 100000...然后您可以再次看到速度的提高。但是,如果您实际上不必用零预填充它并且可以使用空数组的标准错误。然后(arr = []).length = x在我的测试用例中大部分时间都非常快。
2021-03-18 20:00:14
填充很快。new Array(len)是痛苦的缓慢。(arr = []).length = len; arr.fill(0);是我在任何地方看到的最快的解决方案......或者至少是捆绑的
2021-03-20 20:00:14
@PimpTrizkitarr = Array(n)(arr = []).length = n根据规范表现相同。在某些实现中,可能会更快,但我认为没有太大区别。
2021-03-27 20:00:14

虽然这是一个旧线程,但我想添加我的 2 美分。不确定这是多慢/多快,但它是一个快速的单班轮。这是我所做的:

如果我想预先填写一个数字:

Array.apply(null, Array(5)).map(Number.prototype.valueOf,0);
// [0, 0, 0, 0, 0]

如果我想用字符串预填充:

Array.apply(null, Array(3)).map(String.prototype.valueOf,"hi")
// ["hi", "hi", "hi"]

其他答案建议:

new Array(5+1).join('0').split('')
// ["0", "0", "0", "0", "0"]

但是如果你想要 0(数字)而不是“0”(字符串中的零),你可以这样做:

new Array(5+1).join('0').split('').map(parseFloat)
// [0, 0, 0, 0, 0]
(顺便说一句,我们并不真正需要new)当您这样做时,Array(5)您正在创建一个看起来像这样的对象:{ length: 5, __proto__: Array.prototype }- 尝试console.dir( Array(5) )请注意,它没有任何属性012等。但是当您apply使用Array构造函数时,就像在说Array(undefined, undefined, undefined, undefined, undefined)你会得到一个有点像 的对象{ length: 5, 0: undefined, 1: undefined...}map适用于 properties 01等,这就是为什么您的示例不起作用,但是当您使用apply它时。
2021-03-22 20:00:14
Array.apply(null, Array(5)).map(x=>0) 怎么样?时间有点短!
2021-03-27 20:00:14
同样用 size +assign 初始化比 push 快得多。参见测试用例jsperf.com/zero-fill-2d-array
2021-03-31 20:00:14
for的第一个参数.apply实际上是您想要的this对于这些目的,this这并不重要——我们只真正关心参数传播的“特征” .apply——所以它可以是任何值。我喜欢null它,因为它很便宜,你可能不想使用,{}或者[]因为你会无缘无故地实例化一个对象。
2021-04-06 20:00:14
很好的答案!你能解释一下这个技巧Array.apply(null, new Array(5)).map(...)吗?因为简单地做 (new Array(5)).map(...) 不会像规范所说的那样工作
2021-04-09 20:00:14

简而言之

最快的解决方案

let a = new Array(n); for (let i=0; i<n; ++i) a[i] = 0;

最短(方便)的解决方案(小数组慢 3 倍,大数组稍慢(在 Firefox 上最慢))

Array(n).fill(0)


细节

今天 2020.06.09 我在 Chrome 83.0、Firefox 77.0 和 Safari 13.1 浏览器上对 macOS High Sierra 10.13.6 进行了测试。我为两个测试用例测试了选定的解决方案

  • 小数组 - 有 10 个元素 - 你可以在这里执行测试
  • 大阵列 - 有 100 万个元素 - 您可以在此处执行测试

结论

  • 基于new Array(n)+for(N) 的解决方案是小阵列和大阵列的最快解决方案(Chrome 除外,但在那里仍然非常快),建议将其作为快速跨浏览器解决方案
  • 基于new Float32Array(n)(I) 的解决方案返回非典型数组(例如,您无法调用push(..)它),因此我不会将其结果与其他解决方案进行比较-但是,此解决方案比所有浏览器上的大阵列的其他解决方案快 10-20 倍
  • 基于for(L,M,N,O) 的解决方案对于小阵列来说很快
  • 基于fill(B,C) 的解决方案在 Chrome 和 Safari 上速度很快,但在 Firefox 上对于大数组却出奇地慢。对于小型阵列,它们的速度中等
  • 基于Array.apply(P) 的解决方案对大数组抛出错误

在此处输入图片说明

代码和示例

下面的代码提供了测量中使用的解决方案

Chrome 的示例结果

在此处输入图片说明

如果我的回答有帮助你可以给我买杯咖啡

刚刚在 Chrome 77 上运行了一些测试,一个简单的 push() 循环比 fill() 快两倍......我想知道 fill() 有哪些微妙的副作用会阻止更有效的实现?
2021-03-15 20:00:14
不错的测量。分析:G 很慢,因为每次迭代都调整数组大小,调整大小意味着进行新的内存分配。A,B,M 很快,因为尺寸调整只进行一次。+1
2021-03-17 20:00:14
@EricGrange 我更新了答案——在底部我用你的提议更新了 benchamrk 的链接:案例 P——let a=[]; for(i=n;i--;) a.push(0);但它比它慢 4 倍fill(0)——所以我什至不会更新那个案例的图片。
2021-03-20 20:00:14
@Roland 我想你的意思是 N 而不是 M?
2021-03-23 20:00:14
for-loop(N).fill在 Safari中仅比(C)1.835,有趣的是,当我现在运行它时,6 个月后,差异仅下降到 1.456 倍。所以对于 Safari,最快的解决方案 (N) 只比最短和最简单的版本快 45%。道德:坚持使用最短和最简单的版本(对于大多数情况,如果不是所有情况)。它通过读取更快、更易于维护来节省昂贵的开发人员时间,并且随着时间和 CPU 速度的增加,它的回报也越来越多,而无需额外的维护。
2021-04-04 20:00:14

用预先计算的值填充数组的优雅方式

这是使用 ES6 的另一种方法,到目前为止没有人提到过:

> Array.from(Array(3), () => 0)
< [0, 0, 0]

它的工作原理是传递一个 map 函数作为 的第二个参数Array.from

在上面的例子中,第一个参数分配一个由 3 个位置填充的数组,undefined然后 lambda 函数将每个位置映射到 value 0

虽然Array(len).fill(0)较短,但如果您需要先进行一些计算来填充数组,它就不起作用(我知道这个问题没有要求它,但很多人最终在这里寻找这个)

例如,如果您需要一个包含 10 个随机数的数组:

> Array.from(Array(10), () => Math.floor(10 * Math.random()))
< [3, 6, 8, 1, 9, 3, 0, 6, 7, 1]

它比等效的更简洁(和优雅):

const numbers = Array(10);
for (let i = 0; i < numbers.length; i++) {
    numbers[i] = Math.round(10 * Math.random());
}

通过利用回调中提供的 index 参数,此方法还可用于生成数字序列:

> Array.from(Array(10), (d, i) => i)
< [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

奖励答案:使用字符串填充数组 repeat()

由于这个答案得到了很多关注,我也想展示这个很酷的技巧。虽然不如我的主要答案有用,但将介绍仍然不太为人所知但非常有用的 Stringrepeat()方法。这是诀窍:

> "?".repeat(10).split("").map(() => Math.floor(10 * Math.random()))
< [5, 6, 3, 5, 0, 8, 2, 7, 4, 1]

很酷吧?repeat()是一种非常有用的方法来创建一个字符串,该方法是将原始字符串重复一定次数。之后,split()为我们创建一个数组,然后map()将其输入到我们想要的值。分步分解:

> "?".repeat(10)
< "??????????"

> "?".repeat(10).split("")
< ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]

> "?".repeat(10).split("").map(() => Math.floor(10 * Math.random()))
< [5, 6, 3, 5, 0, 8, 2, 7, 4, 1]
不是真的,Array.from() 这里基本上是创建一个数组,用 map() 遍历它,在每个项目上调用一个函数来创建一个新数组,然后丢弃第一个数组......对于一个小数组,这可能是无害,对于较大的数组,这种模式导致人们称浏览器为“内存猪”:)
2021-03-10 20:00:14
处理大型数组的人应该比这更清楚,当然。但是,对于常见的应用程序,创建一个可以立即处理的常规大小的辅助数组(最多 10k 个元素)是非常好的(花费的时间与避免额外数组创建的时间相同 - 使用最新的 Chrome 测试)。对于这种情况,可读性变得比微小的性能优化更重要。关于 O(n) 时间,如果您需要为每个元素计算不同的东西(我的答案的主要主题),这是必要的。这个讨论很有趣,很高兴你提出来了!
2021-03-22 20:00:14
那篇文章中有很多客厅技巧,但希望没有一个能达到生产代码:)
2021-03-31 20:00:14
虽然repeat在生产中绝对不需要这个技巧,但Array.from()非常好:-)
2021-04-01 20:00:14

已经提到的 ES 6 填充方法很好地解决了这个问题。截至今天,大多数现代桌面浏览器已经支持所需的 Array 原型方法(Chromium、FF、Edge 和 Safari)[ 1 ]。您可以在MDN上查找详细信息一个简单的使用示例是

a = new Array(10).fill(0);

鉴于当前的浏览器支持,除非您确定您的受众使用现代桌面浏览器,否则您应该谨慎使用它。

这将适用于数组。 a = Array(10).fill(null).map(() => { return []; });
2021-03-27 20:00:14
2016 年更新:此方法将其他所有内容都排除在外,单击此处查看基准: jsfiddle.net/basickarl/md5z0Lqq
2021-03-28 20:00:14
@AndrewAnthonyGerst Terser: a = Array(10).fill(0).map( _ => [] );
2021-03-31 20:00:14
如果您填写引用类型,则所有引用类型都将是相同的引用。new Array(10).fill(null).map(() => []) 将是一种简洁的方法来解决这个问题(最初让我感到震惊,哈哈)
2021-04-05 20:00:14