Javascript排序函数错误地更改了数组

时间:2016-03-25 01:48:09

标签: javascript arrays sorting

我正在使用我的AngularJS应用程序的数组排序功能。它使用变量direction来确定是以升序(direction === -1)还是降序(direction === 1)方式对数据进行排序。

出现了什么问题:有时当我进行排序时,数组中应该位于相同位置的元素会返回到不同的位置,而数组中的任何内容都不会实际更改。

例如,如果我有:

   var arr = [
       {id: 3, name: 'd'},
       {id: 2, name: 'b'},
       {id: 1, name: 'a'},
       {id: 2, name: 'c'}
   ];

我按“id”排序方向为-1,它会返回名称为“a,b,c,d”,正如您所期望的那样。然后我会再次排序(将方向改为1),它将反转方向。但是如果我再次对它进行排序(使用direction === -1),它将以“a,c,b,d”的顺序返回。

这是一个简化的例子;实际上它远没有那么可预测。

我有一种感觉,我没有正确使用direction。见下文:

this.sortData = function (data, type, direction) {

    return data.sort(sortFunct);

    function sortFunct(a, b) {
        var numberTypes = ['Thread', 'Job', 'FolderCount', 'MessageCount', 'EmailCount', 'CalendarCount', 'TaskCount', 'OtherCount', 'JobId', 'BatchId', 'ItemsTotal', 'ItemsRemaining', 'ItemsFailed', 'Size', 'progress', 'PercentComplete'];
        var stringTypes = ['Level', 'StatusMessage', 'Author', 'ItemStatus', 'JobStatus', 'SourceMailbox', 'TargetMailbox', 'Subject', 'Folder', 'MessageClass', 'StatusMessage', 'Path', 'Owner1',];

        if (numberTypes.indexOf(type) !== -1) {
            return direction * (a[type] - b[type]);
        } else if (stringTypes.indexOf(type) !== -1) {
            if (!a[type]) {
                return 1;
            } else if (!b[type]) {
                return -1;
            } else {
                return a[type].localeCompare(b[type]) * direction;
            }
        } else if (type === 'DiscoveryDate' || type === 'ReceivedDate' || type === 'Timestamp') {
            if (a[type] > b[type]) {
                return direction * 1;
            } else if (a[type] < b[type]) {
                return direction * -1;
            } else {
                return 0;
            }
        } else {
            return direction * (a[type] - b[type]);
        }
    } // End sortFunct
};

3 个答案:

答案 0 :(得分:17)

.sort()的ECMAScript规范并不要求它是稳定的,因此您的自定义排序函数所说的元素相同(例如,返回0)将无法保证每个元素的顺序事实上,他们最终订购的订单可能会受到排序前的订单的影响。

如果你想要一致的排序,那么你需要永远不要从你的自定义排序函数返回0,除非这两个项完全相同,以至于你永远不知道哪个是哪个。

当主键相同时,您需要测试辅助键并返回该比较的结果。如果辅助键是相同的,那么比较一个三级键等等,只要它值得你去。

这种二次比较可以使排序稳定,以便它始终以可预测的顺序着陆,无论之前的顺序如何。

我也看到了排序方案,它创建了一个唯一的密钥,并将其分配给每个对象,并将其用作二级或三级密钥,从而保证如果您关心的密钥相同,则可以比较唯一密钥。密钥并始终具有一致的稳定排序。

以下是如果比较字段(此时的年龄具有相同值)保留原始订单的方案示例。要知道原始订单是什么,它会使用origOrder属性标记每个对象,以识别原始订单。

var data = [
  {name: "Amy", age: 13},
  {name: "Anne", age: 13},
  {name: "John", age: 11},
  {name: "Jack", age: 12},
  {name: "Ted", age: 11},
  {name: "Alice", age: 12}
];
  
// mark each object with the original order  
data.forEach(function(item, index) {
  item.origOrder = index;
});  
  
data.sort(function(a, b) {
  var diff = a.age - b.age;
  if (diff !== 0) {
    return diff;
  } else {
    return a.origOrder - b.origOrder;
  }
});  

// show results in snippet
document.write(JSON.stringify(data));  

使用ES6中的Map对象,我们也可以在不触及数组中的原始对象的情况下通过创建用于解析关系并将其存储在Map对象中的临时索引来执行此操作。这可能是这样的:

    var data = [
      {name: "Amy", age: 13},
      {name: "Anne", age: 13},
      {name: "John", age: 11},
      {name: "Jack", age: 12},
      {name: "Ted", age: 11},
      {name: "Alice", age: 12}
    ];
    
    // put each object in a temporary map with its original index
    let tempMap = new Map();
    data.forEach((item, index) => {
        tempMap.set(item, index);
    });
      
    data.sort(function(a, b) {
      var diff = a.age - b.age;
      if (diff !== 0) {
        return diff;
      } else {
        return tempMap.get(a) - tempMap.get(b);
      }
    });  

    // show results in snippet
    document.write(JSON.stringify(data));  

通过查看排序结果,您可以看到保留两个具有相同年龄的条目的相对顺序,而无需以任何方式修改原始对象。

答案 1 :(得分:2)

ECMAScript不要求排序稳定(related question)。此外,即使排序稳定,您也不能期望代码知道何时应该反转相同的记录(2和2)。为了能够做到这一点,你不能从比较器返回0 - 即在平局的情况下,你需要一个辅助键来帮助消除秩序的歧义。如果你无法想到更好的事情,你可以给每个对象a unique ID

答案 2 :(得分:1)

&#34; sort()方法对数组中的元素进行排序并返回数组。排序必然稳定。&#34;

请参阅:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

还有:

https://en.wikipedia.org/wiki/Sorting_algorithm#Stability

&#39; HTH,