在 Javascript 中反转数组的最有效方法是什么?

IT技术 javascript arrays performance
2021-01-31 13:46:17

最近有人问我在 Javascript 中反转数组的最有效方法是什么。目前,我建议使用 for 循环并摆弄数组,但后来意识到有一个本地Array.reverse()方法。

出于好奇,任何人都可以通过展示示例或指向正确的方向来帮助我探索这一点,以便我可以阅读此内容吗?任何关于如何衡量性能的建议也会很棒。

6个回答

基于此设置:

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

跨浏览器,交换循环更快。有两种常见类型的交换算法(请参阅维基百科),每种算法都有两种变体。

两种交换算法是临时交换和异或交换。

这两种变体以不同的方式处理索引计算。第一个变体比较当前的左索引和右索引,然后递减数组的右索引。第二个变体比较当前的左索引和除以一半的长度,然后为每次迭代重新计算右索引。

您可能会也可能不会看到这两种变体之间的巨大差异。例如,在 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;
}

异或交换:

第一个变化:

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();方法的算法是最短的例子,它们看起来最干净。我希望他们的表现在未来变得更好。


另一个提到的是现代浏览器正在提高其数组pushsplice操作的性能

在 Firefox 10 中,这种for使用数组的循环算法可以pushsplice临时交换和 XOR 交换循环算法相媲美。

for (length -= 2; length > -1; length -= 1)
{
    array.push(array[length]);
    array.splice(length, 1);
}

但是,您可能应该坚持使用交换循环算法,直到许多其他浏览器匹配或超过它们的数组pushsplice性能。

这些代码建议质量很差(像 ANSI C 一样编写)并且基准测试不正确。不要过早地优化,array.reverse()如果需要就地或array.slice().reverse()需要副本,请使用内置函数标准库可能已经是最快的,无疑是最容易理解的,并且随着时间的推移进行优化,它们的速度会免费提高。
2021-03-16 13:46:17
是的,在 2016 年的 Google Chrome 中,array.reverse 至少比其他任何东西快两倍。请参阅其他评论中的 jsperf 链接。
2021-03-21 13:46:17
鲍里斯是对的:jsperf.com/js-array-reverse-vs-while-loop/9此外,在 Google Chrome array.reverse 中比其他方法更快
2021-03-25 13:46:17
我认为应该注意上面的所有函数,除了临时交换之外,只适用于整数数组,而不适用于其他类型(字符串、对象等)的数组。
2021-03-28 13:46:17
您的基准测试的“推送然后切片”测试破坏了 global length,使其之后的所有测试都毫无意义。
2021-04-04 13:46:17

以简单的方式,您可以使用地图来做到这一点。

let list = [10, 20, 30, 60, 90]
let reversedList = list.map((e, i, a)=> a[(a.length -1) -i]) // [90, 60...]
这是一个不会改变原始数组的解决方案!+1
2021-03-20 13:46:17

本机方法总是更快。

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

@Matt McDonald 有趣,我们刚刚在大学里做了一个项目,证明不然,哈哈。尽管我们测试了各种功能,例如冒泡排序、堆排序、快速排序。像这样的事情的时间与数组的布局方式有很大关系。(是否已经排序,是否已经随机?)不同类型的排序/反向排序通常取决于数组的初始状态。很难证明哪个最快,因为它们有许多不同的场景。
2021-03-20 13:46:17
改进你的算法,然后再试一次。现在,Array.reverse();是第一个还是第二个最慢!
2021-03-22 13:46:17
for (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;)”只是糟糕的基准测试——初始化和递减是在循环初始化程序内部还是在循环初始化程序之外根本无关紧要。
2021-03-24 13:46:17
在 Javascript 中,本地方法几乎总是较慢,因为语言的设计从根本上被破坏了请参阅绑定与关闭for 循环与 mapfor 与 .forEach这是因为内置函数无法对数组做出您可以做出的假设,因此必须执行更多的测试。如果你关心性能,考虑做你自己的循环或 lodash;使用本机方法来提高可读性。
2021-03-30 13:46:17
@Raynos 你的例子坏了;它不会迭代整个数组。它应该读var i = ii - 1;i >= 0;i--
2021-04-08 13:46:17

在 Firefox 中打开了一个关于缓慢反向性能的 Firefox 错误Mozilla 的某个人查看了接受的帖子中使用的基准,并说它具有误导性——在他们的分析中,本机方法通常更适合反转数组。(理应如此!)

如果人们对这个主题感兴趣,他们需要通读整个错误案例。自从您发布答案以来,似乎还有更多内容,但我认为我肯定会坚持使用 native Array.reverse()
2021-04-10 13:46:17

这是使用三元运算符反转数组的最有效和最干净的方法。

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))) .
2021-03-17 13:46:17