最有效的方法来创建一个零填充的JavaScript数组?
在JavaScript中创建任意长度的零填充数组的最有效方法是什么?
虽然这是一个古老的线程,我想增加我的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]
ES6引入了Array.prototype.fill
。 它可以像这样使用:
new Array(len).fill(0);
不知道是否快,但我喜欢它,因为它很短,自我描述。
2013年8月更新,2015年2月更新:2009年以下的答案与JavaScript的通用Array
类型有关。 它不涉及ES2015中定义的更新类型的数组[现在在许多浏览器中可用],如Int32Array
等。 另请注意,ES2015为阵列和类型数组添加了fill
方法,这可能是最有效的填充方法…
此外,它可以使一些实现如何创建数组有很大的不同。 特别是Chrome的V8引擎,如果认为可以的话,会尝试使用高效率的连续内存阵列,只有在必要的时候才转移到基于对象的阵列。
对于大多数语言,它将被预先分配,然后填零,如下所示:
function newFilledArray(len, val) { var rv = new Array(len); while (--len >= 0) { rv[len] = val; } return rv; }
但是 ,JavaScript数组并不是真正的数组 ,它们就像所有其他JavaScript对象一样是键/值映射,所以没有“预分配”来做(设置长度不会分配多个槽来填充),也不是有什么理由相信,当实现可能已经优化了它们对密钥的处理时,倒计数到零的好处(这只是为了快速地在循环中进行比较)并没有通过以相反的顺序添加密钥来抵消与理论上的数组相关,您将通常按顺序执行它们。
实际上,Matthew Crumley指出Firefox的倒数明显比倒数慢,我可以肯定的一个结果是它的数组部分(循环到零仍然快于循环到var的限制)。 显然按相反的顺序将元素添加到数组中是Firefox上的一个缓慢的操作。 实际上,JavaScript实现的结果差别很大(这并不令人惊讶)。 下面是浏览器实现的一个快速和肮脏的测试页面(非常脏,不会在测试期间产生,所以提供最少的反馈,并且将违背脚本时间限制)。 我建议在测试之间刷新; 如果你不这样做,FF(至少)会在重复测试中变慢。
使用Array#concat的相当复杂的版本比FF上的直接初始化更快,因为它介于1,000到2,000个元素数组之间。 不过,在Chrome的V8引擎上,每次都是直接启动
这里是测试页面( 现场复制 ):
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Zero Init Test Page</title> <style type='text/css'> body { font-family: sans-serif; } #log p { margin: 0; padding: 0; } .error { color: red; } .winner { color: green; font-weight: bold; } </style> <script type='text/javascript' src='prototype-1.6.0.3.js'></script> <script type='text/javascript'> var testdefs = { 'downpre': { total: 0, desc: "Count down, pre-decrement", func: makeWithCountDownPre }, 'downpost': { total: 0, desc: "Count down, post-decrement", func: makeWithCountDownPost }, 'up': { total: 0, desc: "Count up (normal)", func: makeWithCountUp }, 'downandup': { total: 0, desc: "Count down (for loop) and up (for filling)", func: makeWithCountDownArrayUp }, 'concat': { total: 0, desc: "Concat", func: makeWithConcat } }; document.observe('dom:loaded', function() { var markup, defname; markup = ""; for (defname in testdefs) { markup += "<div><input type='checkbox' id='chk_" + defname + "' checked>" + "<label for='chk_" + defname + "'>" + testdefs[defname].desc + "</label></div>"; } $('checkboxes').update(markup); $('btnTest').observe('click', btnTestClick); }); function epoch() { return (new Date()).getTime(); } function btnTestClick() { // Clear log $('log').update('Testing...'); // Show running $('btnTest').disabled = true; // Run after a pause while the browser updates display btnTestClickPart2.defer(); } function btnTestClickPart2() { try { runTests(); } catch (e) { log("Exception: " + e); } // Re-enable the button; we don't yheidl $('btnTest').disabled = false; } function runTests() { var start, time, counter, length, defname, def, results, a, invalid, lowest, s; // Get loops and length s = $F('txtLoops'); runcount = parseInt(s); if (isNaN(runcount) || runcount <= 0) { log("Invalid loops value '" + s + "'"); return; } s = $F('txtLength'); length = parseInt(s); if (isNaN(length) || length <= 0) { log("Invalid length value '" + s + "'"); return; } // Clear log $('log').update(''); // Do it for (counter = 0; counter <= runcount; ++counter) { for (defname in testdefs) { def = testdefs[defname]; if ($('chk_' + defname).checked) { start = epoch(); a = def.func(length); time = epoch() - start; if (counter == 0) { // Don't count (warm up), but do check the algorithm works invalid = validateResult(a, length); if (invalid) { log("<span class='error'>FAILURE</span> with def " + defname + ": " + invalid); return; } } else { // Count this one log("#" + counter + ": " + def.desc + ": " + time + "ms"); def.total += time; } } } } for (defname in testdefs) { def = testdefs[defname]; if ($('chk_' + defname).checked) { def.avg = def.total / runcount; if (typeof lowest != 'number' || lowest > def.avg) { lowest = def.avg; } } } results = "<p>Results:" + "<br>Length: " + length + "<br>Loops: " + runcount + "</p>"; for (defname in testdefs) { def = testdefs[defname]; if ($('chk_' + defname).checked) { results += "<p" + (lowest == def.avg ? " class='winner'" : "") + ">" + def.desc + ", average time: " + def.avg + "ms</p>"; } } results += "<hr>"; $('log').insert({top: results}); } function validateResult(a, length) { var n; if (a.length != length) { return "Length is wrong"; } for (n = length - 1; n >= 0; --n) { if (a[n] != 0) { return "Index " + n + " is not zero"; } } return undefined; } function makeWithCountDownPre(len) { var a; a = new Array(len); while (--len >= 0) { a[len] = 0; } return a; } function makeWithCountDownPost(len) { var a; a = new Array(len); while (len-- > 0) { a[len] = 0; } return a; } function makeWithCountUp(len) { var a, i; a = new Array(len); for (i = 0; i < len; ++i) { a[i] = 0; } return a; } function makeWithCountDownArrayUp(len) { var a, i; a = new Array(len); i = 0; while (--len >= 0) { a[i++] = 0; } return a; } function makeWithConcat(len) { var a, rem, currlen; if (len == 0) { return []; } a = [0]; currlen = 1; while (currlen < len) { rem = len - currlen; if (rem < currlen) { a = a.concat(a.slice(0, rem)); } else { a = a.concat(a); } currlen = a.length; } return a; } function log(msg) { $('log').appendChild(new Element('p').update(msg)); } </script> </head> <body><div> <label for='txtLength'>Length:</label><input type='text' id='txtLength' value='10000'> <br><label for='txtLoops'>Loops:</label><input type='text' id='txtLoops' value='10'> <div id='checkboxes'></div> <br><input type='button' id='btnTest' value='Test'> <hr> <div id='log'></div> </div></body> </html>
已经提到的ES 6填充方法照顾好了这一点。 到目前为止,大多数现代桌面浏览器已经支持所需的数组原型方法(Chromium,FF,Edge和Safari)[ 1 ]。 你可以在MDN上查询细节。 一个简单的使用例子是
a = new Array(10).fill(0);
鉴于目前的浏览器支持,你应该谨慎使用这个,除非你确定你的观众使用现代桌面浏览器。
默认情况下, Uint8Array
, Uint16Array
和Uint32Array
类将零值保留为其值,所以你不需要任何复杂的填充技术,只需要:
var ary = new Uint8Array(10);
数组ary
所有元素将默认为零。
用预先计算的值填充数组的优雅方式
这是另一种使用ES6的方法,目前为止没有人提到:
> Array.from(Array(3), () => 0) < [0, 0, 0]
它通过传递一个map函数作为Array.from
的第二个参数。
在上面的例子中,第一个参数分配了一个由3个位置组成的数组,其值为undefined
,然后lambda函数将它们中的每一个映射到值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()); }
这个方法还可以用来通过利用回调中提供的索引参数来生成数字序列:
> Array.from(Array(10), (d, i) => i) < [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
奖金答案:使用String repeat()
填充数组
由于这个答案得到了很多的关注,我也想展示这个很酷的伎俩。 虽然没有我的主要答案那样有用,但是会介绍还不是很有名,但是很有用的String repeat()
方法。 诀窍是:
> "?".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]
function makeArrayOf(value, length) { var arr = [], i = length; while (i--) { arr[i] = value; } return arr; } makeArrayOf(0, 5); // [0, 0, 0, 0, 0] makeArrayOf('x', 3); // ['x', 'x', 'x']
请注意, while
通常比for-in
, forEach
等效率更高
我测试了在IE 6/7/8,Firefox 3.5,Chrome和Opera中预先分配/不预分配,向上/向下计数和for / while循环的所有组合。
下面的功能在Firefox,Chrome和IE8中一直是最快或者非常接近的,而且在Opera和IE 6中速度也不是最快。在我看来,它也是最简单和最清晰的。 我发现几个浏览器的while循环版本稍微快一点,所以我也包括它作为参考。
function newFilledArray(length, val) { var array = []; for (var i = 0; i < length; i++) { array[i] = val; } return array; }
要么
function newFilledArray(length, val) { var array = []; var i = 0; while (i < length) { array[i++] = val; } return array; }
function zeroFilledArray(size) { return new Array(size + 1).join('0').split(''); }
使用对象符号
var x = [];
零填充? 喜欢…
var x = [0,0,0,0,0,0];
充满'未定义'…
var x = new Array(7);
obj符号与零
var x = []; for (var i = 0; i < 10; i++) x[i] = 0;
作为一个侧面说明,如果你修改阵列的原型,两者
var x = new Array();
和
var y = [];
将有这些原型修改
无论如何,我不会过分关心这个操作的效率和速度,还有很多其他的东西你可能会做,比包含零的任意长度的数组更为浪费和昂贵。
如果您需要在执行代码的过程中创建多个不同长度的零填充数组,我发现实现这个目的的最快方法是使用本主题中提到的方法之一创建一个零数组,你知道永远不会被超过,然后根据需要切片该数组。
例如(使用上面选择的函数来初始化数组),创建一个长度为maxLength的零填充数组,作为需要零数组的代码可见的变量:
var zero = newFilledArray(maxLength, 0);
现在,每当你需要一个长度为零的数组时,切分这个数组requiredLength < maxLength :
zero.slice(0, requiredLength);
我在执行代码的时候创建了数以千计的零填充数组,这极大地加速了这个过程。
如果你使用ES6,你可以像这样使用Array.from() :
Array.from({ length: 3 }, () => 0); //[0, 0, 0]
我通常这样做(而且速度惊人)是使用Uint8Array
。 例如,创建一个1M元素的零填充矢量:
var zeroFilled = [].slice.apply(new Uint8Array(1000000))
我是一个Linux用户,总是为我工作,但一旦使用Mac的朋友有一些非零元素。 我以为他的机器发生故障,但仍然是我们发现修复它的最安全的方式:
var zeroFilled = [].slice.apply(new Uint8Array(new Array(1000000))
编辑
Chrome 25.0.1364.160
- 弗雷德里克·戈特利布 – 6.43
- Sam Barnum – 4.83
- Eli – 3.68
- 约书亚记2.91
- Mathew Crumley – 2.67
- bduran – 2.55
- 艾伦·赖斯 – 2.11
- kangax – 0.68
- TJ。 Crowder – 0.67
- zertosh – 错误
Firefox 20.0
- 艾伦·赖斯 – 1.85
- 约书亚 – 1.82
- Mathew Crumley – 1.79
- bduran – 1.37
- Frederik Gottlieb – 0.67
- 萨姆·巴纳姆 – 0.63
- Eli – 0.59
- kagax – 0.13
- TJ。 Crowder – 0.13
- zertosh – 错误
缺少最重要的测试(至少对我来说):Node.js之一。 我怀疑它接近Chrome基准。
使用lodash或下划线
_.range(0, length - 1, 0);
或者如果你有一个数组存在,你想要一个相同长度的数组
array.map(_.constant(0));
我没有什么反对的:
Array.apply(null, Array(5)).map(Number.prototype.valueOf,0); new Array(5+1).join('0').split('').map(parseFloat);
由Zertosh建议,但在一个新的ES6数组扩展允许您使用fill
方法本地执行此操作。 现在IE边缘,Chrome和FF支持它,但检查兼容性表
new Array(3).fill(0)
会给你[0, 0, 0]
。 您可以使用任何值填充数组,如new Array(5).fill('abc')
(偶数对象和其他数组)。
最重要的是,你可以用fill来修改以前的数组:
arr = [1, 2, 3, 4, 5, 6] arr.fill(9, 3, 5) # what to fill, start, end
这给你: [1, 2, 3, 9, 9, 6]
那么new Array(51).join('0').split('')
?
我最快的功能是:
function newFilledArray(len, val) { var a = []; while(len--){ a.push(val); } return a; } var st = (new Date()).getTime(); newFilledArray(1000000, 0) console.log((new Date()).getTime() - st); // returned 63, 65, 62 milliseconds
使用本地push和shift来向数组添加项目要比声明数组作用域并引用每个项目来设置它的值要快得多(大约10倍)。
fyi:我一直用第一个循环获得更快的时间,这是在firebug(firefox扩展)中运行时倒数的。
var a = []; var len = 1000000; var st = (new Date()).getTime(); while(len){ a.push(0); len -= 1; } console.log((new Date()).getTime() - st); // returned 863, 894, 875 milliseconds st = (new Date()).getTime(); len = 1000000; a = []; for(var i = 0; i < len; i++){ a.push(0); } console.log((new Date()).getTime() - st); // returned 1155, 1179, 1163 milliseconds
我很想知道TJ Crowder做了什么? 🙂
我正在测试TJ Crowder的出色答案,并提出了一个基于concat解决方案的递归合并,这个解决方案在Chrome中的测试(我没有测试其他浏览器)上胜出。
function makeRec(len, acc) { if (acc == null) acc = []; if (len <= 1) return acc; var b = makeRec(len >> 1, [0]); b = b.concat(b); if (len & 1) b = b.concat([0]); return b; },
用makeRec(29)
调用该方法。
没有看到这个方法的答案,所以这里是:
"0".repeat( 200 ).split("").map( parseFloat )
结果你将得到长度为200的零值数组:
[ 0, 0, 0, 0, ... 0 ]
我不确定这个代码的性能,但是如果你将它用于相对较小的数组,它不应该成为一个问题。
从ECMAScript2016开始 ,大数组就有一个明确的选择。
由于这个答案仍然显示在谷歌搜索的顶部附近,这里是2017年的答案。
这里有一个目前的jsbench和几十种流行的方法,其中包括到目前为止在这个问题上提出的许多方法。 如果你找到一个更好的方法,请添加,分叉和分享。
我想指出,没有真正的最有效的方法来创建一个任意长度的零填充数组。 您可以针对速度进行优化,或者为了清晰度和可维护性进行优化 – 根据项目的需要,可以将其视为更高效的选择。
在进行速度优化时,您需要:使用文字语法创建数组; 设置长度,初始化迭代变量,并使用while循环遍历数组。 这是一个例子。
const arr = []; arr.length = 120000; let i = 0; while (i < 120000) { arr[i] = 0; i++; }
Chrome(2013-03-21)测试中的这个concat
版本要快得多。 对于10,000,000个元素,大约是200ms,对于直接初始化是675。
function filledArray(len, value) { if (len <= 0) return []; var result = [value]; while (result.length < len/2) { result = result.concat(result); } return result.concat(result.slice(0, len-result.length)); }
奖励:如果你想用字符串填充你的数组,这是一个简洁的方法来做到这一点(尽管不如concat
快):
function filledArrayString(len, value) { return new Array(len+1).join(value).split(''); }
值得指出的是, Array.prototype.fill
已被添加为ECMAScript 6(和谐)提案的一部分 。 在考虑线程中提到的其他选项之前,我宁愿使用下面的填充。
if (!Array.prototype.fill) { Array.prototype.fill = function(value) { // Steps 1-2. if (this == null) { throw new TypeError('this is null or not defined'); } var O = Object(this); // Steps 3-5. var len = O.length >>> 0; // Steps 6-7. var start = arguments[1]; var relativeStart = start >> 0; // Step 8. var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len); // Steps 9-10. var end = arguments[2]; var relativeEnd = end === undefined ? len : end >> 0; // Step 11. var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len); // Step 12. while (k < final) { O[k] = value; k++; } // Step 13. return O; }; }
我知道我有这个原生的地方:)
Array.prototype.init = function(x,n) { if(typeof(n)=='undefined') { n = this.length; } while (n--) { this[n] = x; } return this; } var a = (new Array(5)).init(0); var b = [].init(0,4);
编辑:测试
为了回应约书亚和其他方法,我跑了我自己的基准,我看到完全不同的结果。
以下是我测试的内容:
//my original method Array.prototype.init = function(x,n) { if(typeof(n)=='undefined') { n = this.length; } while (n--) { this[n] = x; } return this; } //now using push which I had previously thought to be slower than direct assignment Array.prototype.init2 = function(x,n) { if(typeof(n)=='undefined') { n = this.length; } while (n--) { this.push(x); } return this; } //joshua's method function newFilledArray(len, val) { var a = []; while(len--){ a.push(val); } return a; } //test m1 and m2 with short arrays many times 10K * 10 var a = new Date(); for(var i=0; i<10000; i++) { var t1 = [].init(0,10); } var A = new Date(); var b = new Date(); for(var i=0; i<10000; i++) { var t2 = [].init2(0,10); } var B = new Date(); //test m1 and m2 with long array created once 100K var c = new Date(); var t3 = [].init(0,100000); var C = new Date(); var d = new Date(); var t4 = [].init2(0,100000); var D = new Date(); //test m3 with short array many times 10K * 10 var e = new Date(); for(var i=0; i<10000; i++) { var t5 = newFilledArray(10,0); } var E = new Date(); //test m3 with long array created once 100K var f = new Date(); var t6 = newFilledArray(100000, 0) var F = new Date();
结果:
IE7 deltas: dA=156 dB=359 dC=125 dD=375 dE=468 dF=412 FF3.5 deltas: dA=6 dB=13 dC=63 dD=8 dE=12 dF=8
So by my reckoning push is indeed slower generally but performs better with longer arrays in FF but worse in IE which just sucks in general (quel surprise).
var str = "0000000...0000"; var arr = str.split("");
usage in expressions: arr[i]*1;
EDIT: if arr
supposed to be used in integer expressions, then please don't mind the char value of '0'. You just use it as follows: a = a * arr[i]
(assuming a
has integer value).
Shortest for loop code
a=i=[];for(;i<100;)a[i++]=0; edit: for(a=i=[];i<100;)a[i++]=0; or for(a=[],i=100;i--;)a[i]=0;
Safe var version
var a=[],i=0;for(;i<100;)a[i++]=0; edit: for(var i=100,a=[];i--;)a[i]=0;
The fastest way to do that is with forEach =)
(we keep backward compatibility for IE < 9)
var fillArray = Array.prototype.forEach ? function(arr, n) { arr.forEach(function(_, index) { arr[index] = n; }); return arr; } : function(arr, n) { var len = arr.length; arr.length = 0; while(len--) arr.push(n); return arr; }; // test fillArray([1,2,3], 'X'); // => ['X', 'X', 'X']
There's always the phpjs solution, which you can find here:
http://phpjs.org/functions/array_fill/
I can't speak for the project (creating a library of javascript functions that mirrors the greater functionality of php) as a whole, but the few functions that I've personally pulled from there have worked like a champ.
I just use :
var arr = [10]; for (var i=0; i<=arr.length;arr[i] = i, i++);
匿名功能:
(function(n) { while(n-- && this.push(0)); return this; }).call([], 5); // => [0, 0, 0, 0, 0]
A bit shorter with for-loop:
(function(n) { for(;n--;this.push(0)); return this; }).call([], 5); // => [0, 0, 0, 0, 0]
Works with any Object
, just change what's inside this.push()
.
You can even save the function:
function fill(size, content) { for(;size--;this.push(content)); return this; }
Call it using:
var helloArray = fill.call([], 5, 'hello'); // => ['hello', 'hello', 'hello', 'hello', 'hello']
Adding elements to an already existing array:
var helloWorldArray = fill.call(helloArray, 5, 'world'); // => ['hello', 'hello', 'hello', 'hello', 'hello', 'world', 'world', 'world', 'world', 'world']
Performance: http://jsperf.com/zero-filled-array-creation/25
What everyone else seems to be missing is setting the length of the array beforehand so that the interpreter isn't constantly changing the size of the array.
My simple one-liner would be Array.prototype.slice.apply(new Uint8Array(length))
But if I were to create a function to do it fast without some hacky workaround, I would probably write a function like this:
var filledArray = function(value, l) { var i = 0, a = []; a.length = l; while(i<l) a[i++] = value; return a; }