最近有人问我在JavaScript中反转数组的最有效方法是什么。目前,我建议使用for循环和调整数组,但后来意识到还有一个本地的Array.reverse()
方法。
出于好奇的目的,有人能帮助我通过示例或指导我正确的方向,使我能够深入了解吗?关于如何衡量性能的任何建议也将非常棒。
最近有人问我在JavaScript中反转数组的最有效方法是什么。目前,我建议使用for循环和调整数组,但后来意识到还有一个本地的Array.reverse()
方法。
出于好奇的目的,有人能帮助我通过示例或指导我正确的方向,使我能够深入了解吗?关于如何衡量性能的任何建议也将非常棒。
基于这个设置:
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var length = array.length;
Array.reverse();
是最慢的第一或第二个方法!
这里有基准测试:
https://jsperf.com/js-array-reverse-vs-while-loop/9
在各个浏览器中,交换循环更快。有两种常见的交换算法(参见 Wikipedia),每种算法都有两种变体。
这两种交换算法是临时交换和XOR交换。
两种变体处理索引计算的方式不同。第一个变体比较当前左侧索引和数组右侧索引,然后将数组右侧索引递减。第二个变体比较当前左侧索引和长度除以二,然后为每次迭代重新计算右侧索引。
您可能会看到两种变体之间存在很大差异,也可能不会。例如,在Chrome 18中,临时交换和XOR交换的第一个变体比第二个变体慢60%以上,但在Opera 12中,临时交换和XOR交换的两种变体性能相似。
临时交换:
第一个变体:
function temporarySwap(array)
{
var left = null;
var right = null;
var length = array.length;
for (left = 0, right = length - 1; left < right; left += 1, right -= 1)
{
var temporary = array[left];
array[left] = array[right];
array[right] = temporary;
}
return array;
}
第二种变化:
function temporarySwapHalf(array)
{
var left = null;
var right = null;
var length = array.length;
for (left = 0; left < length / 2; left += 1)
{
right = length - 1 - left;
var temporary = array[left];
array[left] = array[right];
array[right] = temporary;
}
return array;
}
XOR 交换:
第一种变体:
function xorSwap(array)
{
var i = null;
var r = null;
var length = array.length;
for (i = 0, r = length - 1; i < r; i += 1, r -= 1)
{
var left = array[i];
var right = array[r];
left ^= right;
right ^= left;
left ^= right;
array[i] = left;
array[r] = right;
}
return array;
}
第二种变化:
function xorSwapHalf(array)
{
var i = null;
var r = null;
var length = array.length;
for (i = 0; i < length / 2; i += 1)
{
r = length - 1 - i;
var left = array[i];
var right = array[r];
left ^= right;
right ^= left;
left ^= right;
array[i] = left;
array[r] = right;
}
return array;
}
还有一种名为解构赋值的交换方法:
http://wiki.ecmascript.org/doku.php?id=harmony:destructuring解构赋值:
第一个变化:
function destructuringSwap(array)
{
var left = null;
var right = null;
var length = array.length;
for (left = 0, right = length - 1; left < right; left += 1, right -= 1)
{
[array[left], array[right]] = [array[right], array[left]];
}
return array;
}
第二个变体:
function destructuringSwapHalf(array)
{
var left = null;
var right = null;
var length = array.length;
for (left = 0; left < length / 2; left += 1)
{
right = length - 1 - left;
[array[left], array[right]] = [array[right], array[left]];
}
return array;
}
现在,使用解构赋值的算法是其中最慢的。它甚至比Array.reverse();
还要慢。然而,使用解构赋值和Array.reverse();
方法的算法是最简短的例子,并且它们看起来最干净。我希望它们的性能在未来会得到改善。
另一个需要提到的是,现代浏览器正在改进其数组push
和splice
操作的性能。
在Firefox 10中,使用数组push
和splice
的for
循环算法与临时交换和XOR交换的循环算法不相上下。
for (length -= 2; length > -1; length -= 1)
{
array.push(array[length]);
array.splice(length, 1);
}
然而,在许多其他浏览器的数组push
和splice
性能匹配或超过之前,你应该考虑坚持使用交换循环算法。
length
,导致其后的所有测试都失去了意义。 - Boris Zbarskyarray.reverse()
,如果您想要复制,请使用array.slice().reverse()
。标准库已经很可能是最快的,无疑是最干净易懂的,并且随着时间的推移它们的速度会自动提高。 - ggorlen你可以简单地使用 map 进行此操作。
let list = [10, 20, 30, 60, 90]
let reversedList = list.map((e, i, a)=> a[(a.length -1) -i]) // [90, 60...]
本地方法总是更快的。
因此,在可能的情况下使用Array.reverse
。否则,一个运行时间复杂度为O(1)
的实现会更好 ;)
否则就使用类似这样的东西。
var reverse = function(arr) {
var result = [],
ii = arr.length;
for (var i = ii - 1;i !== 0;i--) {
result.push(arr[i]);
}
return result;
}
有趣的是,如果您使用for
循环的所有三个阶段而不仅仅是其中一个,则速度会更快。
for(var i = ii - 1; i !== 0; i--)
比var i = ii - 1; for(; i-- !== 0;)
更快。
Array.reverse();
是最慢的第一或第二个! - XP1var i = ii - 1;i >= 0;i--
。 - mcNuxfor (var i = ii - 1;i !== 0;i--) {
会导致无限循环。ii = arr.length;
是一个无意义的变量。这是粗糙的代码,请避免使用。"否则最好运行在O(1)的实现 ;)"是不可能的和令人困惑的,我认为这是一个玩笑,因为有眨眼的表情符号,但为什么呢?"for(var i = ii - 1; i !== 0;i--)
比var i = ii - 1;for(;i-- !== 0;)
更快"只是错误的基准测试--初始化和递减是在循环初始化器内部还是外部都没有关系。 - ggorlen我提交了一个有关Firefox反转性能缓慢的bug。Mozilla的一位工作人员查看了已接受帖子中使用的基准测试,并表示它非常误导人,根据他们的分析,本机方法通常更适合反转数组。(这是应该的!)
Array.reverse()
。 - CWSpear这是使用三元运算符来反转数组的最有效和清洁的方式。
function reverse(arr) {
return arr.length < 2 ? arr : [arr.pop()].concat(reverse(arr));
}
console.log(reverse([4, 3, 3, 1]));
concat
,基本上为每个递归调用重新分配和构建整个结果数组),如果由于递归而数组太大,则会导致堆栈溢出(尝试使用reverse(Array(10000).fill(0))
)。 - ggorlen.slice().reverse()
来完成此操作:
const yourArray = ["first", "second", "third", "...", "etc"];
const reversedArray = yourArray.slice().reverse();
console.log(reversedArray);
.slice()
的使用是为了防止修改 yourArray
,因为 .reverse()
是原地操作的。.slice()
是多余的! - Jayesh Chandrapal.reverse()
会改变参数,因此 slice
可以防止修改 yourArray
。 - ggorlen交换函数是最快的。这是我写的一个反转函数,与上面提到的交换函数略有相似但执行速度更快。
function reverse(array) {
var first = null;
var last = null;
var tmp = null;
var length = array.length;
for (first = 0, last = length - 1; first < length / 2; first++, last--) {
tmp = array[first];
array[first] = array[last];
array[last] = tmp;
}
}
由于没有人提出来,并为了完成反转数组的方法列表...
array.sort(function() {
return 1;
})
它比两种while方法都快两倍,但除此之外,速度非常慢。
sort(() => 1)
如何向代码读者传达“反转数组”的含义? - ggorlenvar theArray = ['a', 'b', 'c', 'd', 'e', 'f'];
function reverseArrayInPlace(array) {
for (var i = array.length - 1; i >= 0; i -= 1) {
array.push(array[i]);
}
array.splice(0, array.length / 2);
return array;
};
reverseArrayInPlace(theArray);
console.log(theArray); // -> ["f", "e", "d", "c", "b", "a"]
另一个建议,与上面类似,但使用splice:
var myArray=["one","two","three","four","five","six"];
console.log(myArray);
for(i=0;i<myArray.length;i++){
myArray.splice(i,0,myArray.pop(myArray[myArray.length-1]));
}
console.log(myArray);