在Array.sort的中间阶段发生了什么?

时间:2018-09-17 19:44:10

标签: javascript arrays

在深入研究数组方法时,我决定看一下Array.sort方法中涉及的步骤。看一下下面的代码,以反转数组的顺序:

let arr = [];

for (let i = 1; i < 6; i++) {
  arr.push(i);
}

arr.sort((value1, value2) => {
  console.log(arr);
  console.log(`Comparing ${value1} : ${value2}`);
  return value2 - value1;
});

console.log(arr);

我得到以下输出:

[1, 2, 3, 4, 5]
Comparing 1 : 2
[2, 1, 3, 4, 5]
Comparing 1 : 3
[2, 1, 1, 4, 5]
Comparing 2 : 3
[3, 2, 1, 4, 5]
Comparing 1 : 4
[3, 2, 1, 1, 5]
Comparing 2 : 4
[3, 2, 2, 1, 5]
Comparing 3 : 4
[4, 3, 2, 1, 5]
Comparing 1 : 5
[4, 3, 2, 1, 1]
Comparing 2 : 5
[4, 3, 2, 2, 1]
Comparing 3 : 5
[4, 3, 3, 2, 1]
Comparing 4 : 5
[5, 4, 3, 2, 1]

前两步很有意义,但请看第三步:[2, 1, 1, 4, 5]

为什么我期望[2, 3, 1, 4, 5]时会出现这种情况?

正如您可以看到的那样,这种重复的数字现象一次又一次地出现,直到阵列最终反转为止。我想念什么?显然,每次突变后,arr中不在的某个地方都会保留数组的副本。

3 个答案:

答案 0 :(得分:3)

当数组是小型浏览器时(嗯...至少chrome,safari和node)使用插入排序。您看到的行为是在插入排序循环中间查看数组的结果。您可以使用以下方式重现它:

let arr = [ 1, 2, 3, 4, 5];

function InsertionSort(a, comparefn) {
    let from = 0
    let to = a.length
    for (var i = from + 1; i < to; i++) {
      var element = a[i];
      for (var j = i - 1; j >= from; j--) {
        var tmp = a[j];
        var order = comparefn(tmp, element); //<-- your console.log is peaking at the array here
        if (order > 0) {
          a[j + 1] = tmp;
        } else {
          break;
        }
      }
      a[j + 1] = element;
    }
  };

InsertionSort(arr,  (a,b) => {
    console.log(arr.join(","))
    return b-a
})
console.log(arr)

请记住,这不是必需的实现,因此您不必指望这种行为。

答案 1 :(得分:1)

添加到@ mark-meyer答案。对于浏览器,没有关于如何基于提供给sort方法的回调来比较数字的规范。

例如,Array.sort()有时用于通过以下方式均匀地随机化阵列:

var shuffledArr = arr.sort(() => (Math.random() - 0.5))

在这种情况下

  

如果comparefn不是未定义的,并且不是该数组元素的一致比较函数(请参见下文),则排序顺序是实现定义的。

https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.sort

您可以检查此页以在浏览器内查看随机化结果:http://siri0n.github.io/attic/shuffle-comparison/index.html。比较Chrome和Firefox。不仅如此,Firefox将为不同的字段大小选择不同的排序算法。不是答案,但我希望对这个问题有一个有趣的补充。

答案 2 :(得分:-1)

感谢提出了一个令人惊讶的有趣问题。

有副作用/后果:如果异常发生在比较器回调数组内部,则可以打破(不仅是部分排序):

let a = [1, 3, 2, 6, 4];
let stepToFail = 2;
try { 
   a.sort((x1, x2) => {
     if (!stepToFail--) throw "test"; 
     return x1 - x2;
   }); 
} catch(e) {
   // shows [1,3,3,6,4] in Chrome; data is broken and cannot be used anymore
   console.log(JSON.stringify(a)); 
}

[UPD]我在Chromium项目中报告了a bug,并且由于“无法修复”而被关闭

  

Array.prototype.sort在Chrome 70.0.3533中重新实现。那是   行为发生变化的原因。

     

不管发生什么变化,上面的示例并没有真正   构成一个错误。比较功能不“一致”,并且   根据规范,最终的排序顺序是实现定义的。那   包含“不一致状态”,​​因为可以在   寻找合适的位置放置值或在将其写入之前   回来。