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

lui*_*sgo 58 javascript arrays performance

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

出于好奇心的缘故,任何人都可以通过展示示例或指向正确的方向帮助我探索这一点,以便我可以读到这个吗?关于如何衡量绩效的任何建议也都很棒.

XP1*_*XP1 74

基于此设置:

var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var length = array.length;
Run Code Online (Sandbox Code Playgroud)

Array.reverse(); 是第一个还是第二个最慢的!

基准测试如下:http: //jsperf.com/js-array-reverse-vs-while-loop/5

在浏览器中,交换循环更快.有两种常见类型的交换算法(见维基百科),每种都有两种变体.

两种类型的交换算法是临时交换和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;
}
Run Code Online (Sandbox Code Playgroud)

第二种变化:

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

第二种变化:

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;
}
Run Code Online (Sandbox Code Playgroud)

还有另一种称为解构赋值的交换方法:http://wiki.ecmascript.org/doku.php?id = hash: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;
}
Run Code Online (Sandbox Code Playgroud)

第二种变化:

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,使用解构赋值的算法是最慢的算法.它甚至比Array.reverse();.但是,使用解构赋值和Array.reverse();方法的算法是最简短的例子,它们看起来最干净.我希望将来他们的表现会好转.


另一个提及是现代浏览器正在改进其阵列pushsplice操作的性能.

在Firefox 10中,这种for循环算法使用数组push并且可以splice与临时交换和XOR交换循环算法相媲美.

for (length -= 2; length > -1; length -= 1)
{
    array.push(array[length]);
    array.splice(length, 1);
}
Run Code Online (Sandbox Code Playgroud)

但是,您应该坚持使用交换循环算法,直到许多其他浏览器匹配或超出其阵列pushsplice性能.

  • 鲍里斯是对的:http://jsperf.com/js-array-reverse-vs-while-loop/9.此外,在Google Chrome中,array.reverse比其他方法更快 (10认同)
  • 我认为应该注意上面的所有函数,除了临时交换,只适用于整数数组而不是其他类型的数组(字符串,对象等). (8认同)
  • 你的基准测试"推送然后切片"测试破坏了全局的"长度",使得所有的测试都毫无意义. (7认同)

Ray*_*nos 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;
}
Run Code Online (Sandbox Code Playgroud)

基准!

如果您使用for构造的所有三个阶段而不是仅一个阶段,则有趣的循环更快.

for(var i = ii - 1; i !== 0;i--) 比那更快 var i = ii - 1;for(;i-- !== 0;)

  • 在Javascript中,本机方法几乎总是较慢,因为语言的设计[从根本上打破](http://javascript-puzzlers.herokuapp.com/).参见[bind vs. closure](http://stackoverflow.com/questions/17638305); [for loop vs map](http://stackoverflow.com/questions/6551139); [for versus .forEach](https://coderwall.com/p/kvzbpa).这是因为内置函数无法对数组做出假设,因此必须执行更多的测试.如果你担心性能,可以考虑做自己的循环或lodash; 使用本机方法来提高可读性. (4认同)
  • @Raynos你的例子坏了; 它不会迭代整个数组.它应该是`var i = ii - 1; i> = 0; i - `. (4认同)
  • @Matt McDonald 有趣的是,我们刚刚在大学做了一个项目,证明不然哈哈。尽管我们测试了各种功能,例如冒泡排序、堆排序、快速排序。像这样的事情的时间安排与数组的布局方式有很大关系。(是否已经排序,是否已经随机?)不同类型的排序/反向排序通常取决于数组的初始状态。很难证明哪个最快,因为它们有许多不同的场景。 (2认同)

Sri*_*mam 15

以简单的方式,您可以使用地图执行此操作.

let list = [10, 20, 30, 60, 90]
let reversedList = list.map((e, i, a)=> a[(a.length -1) -i]) // [90, 60...]
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案不会改变原始数组!+1 (6认同)

sta*_*wed 10

在Firefox中打开了一个关于慢速反向性能的Firefox错误.Mozilla的某个人看过接受的帖子中使用的基准测试,并说这是非常误导的 - 在他们的分析中,本机方法通常对于反转数组更好.(应该是!)

  • 如果他们对这个主题感兴趣,人们需要仔细阅读整个bug案例.自从你发布了你的答案后,它似乎还有更多内容,但我认为我肯定会坚持使用原生`Array.reverse()`. (2认同)

Ars*_*oor 5

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

function reverse(arr) {
  return arr.length < 2 ? arr : [arr.pop()].concat(reverse(arr));
}
console.log(reverse([4, 3, 3, 1]));
Run Code Online (Sandbox Code Playgroud)

  • 这根本没有效率——它是二次的(在循环中调用“concat”,基本上为每个递归调用重新分配和构建整个结果数组),并且如果数组由于递归而太大,则会破坏堆栈(尝试它与`reverse(Array(10000).fill(0))`)。 (3认同)