【问题标题】:Sort array items and preserve order of same elements对数组项进行排序并保留相同元素的顺序
【发布时间】:2015-07-03 20:15:34
【问题描述】:

假设我有这个数组:

var array = [
  { name: "border-color", value: "#CCCCCC" },
  { name: "color", value: "#FFFFFF" },
  { name: "background-color", value: "rgb(0, 0, 0)" },
  { name: "background-color", value: "rgba(0, 0, 0, .5)" }
];

这个函数按名称对数组进行排序:

array.sort(function(a, b) {
  if (a.name < b.name) return -1;
  if (a.name > b.name) return 1;
  return 0;
});

还有ECMAScript language specifications that tell me that:

排序不一定是稳定的(即比较的元素 equal 不一定保持原来的顺序)。

所以,排序后,name = background color 的两项可以按任意顺序出现,即:

[
  { name: "background-color", value: "rgb(0, 0, 0)" },
  { name: "background-color", value: "rgba(0, 0, 0, .5)" },
  ...
]

或者

[
  { name: "background-color", value: "rgba(0, 0, 0, .5)" },
  { name: "background-color", value: "rgb(0, 0, 0)" },
  ...
]

如何对数组进行排序,以使具有相同名称的项目保持其相对顺序?我宁愿不硬编码任何东西。

【问题讨论】:

标签: javascript arrays sorting


【解决方案1】:

理论上,在排序之前,您可以跟踪其在数组中的索引,并在排序时将其考虑在内。

var sortArray = yourarray.map(function(data, idx){
    return {idx:idx, data:data}
})

sortArray.sort(function(a, b) {
  if (a.data.name < b.data.name) return -1;
  if (a.data.name > b.data.name) return 1;
  return a.idx - b.idx
});

var answer = sortArray.map(function(val){
    return val.data
});

【讨论】:

  • 你从哪里得到idx
  • 很好,修正了地图函数的参数名称
  • @noveyak 由于数组包含引用类型的对象,因此可以在排序函数中确定元素的索引。所以我们避免了排序前后的map。
【解决方案2】:

这改变了as of ES2019Array#sortstable,这意味着具有相同name 的两个条目将具有与数组排序之前相同的相对位置:

22.1.3.27 Array.prototype.sort (comparefn)

此数组的元素已排序。 排序必须稳定(即比较相等的元素必须保持原来的顺序)。

(我的重点)

因此,在您的情况下,{ name: "background-color", value: "rgb(0, 0, 0)" }{ name: "background-color", value: "rgba(0, 0, 0, .5)" } 的顺序将保持不变,因为它们比较相等。

在 ES2019 之前,排序并不要求是稳定的,因此它们可以颠倒顺序 - 或不颠倒,具体取决于实现。

稳定排序是broadly supported(尤其是因为大多数引擎已经实现了稳定排序,所以他们无事可做)。

【讨论】:

【解决方案3】:

给数组增加一个额外的属性:order

var array = [
    { name: "border-color", value: "#CCCCCC", order: 1 },
    { name: "color", value: "#FFFFFF", order: 2 },
    { name: "background-color", value: "rgb(0, 0, 0)", order: 3 },
    { name: "background-color", value: "rgba(0, 0, 0, .5)", order: 4 }
];

如果名称相等,则将排序函数更改为按顺序排序:

array.sort(function(a, b) {
    if (a.name < b.name) return -1;
    if (a.name > b.name) return 1;
    if (a.name == b.name) {
        if(a.order > b.order) return -1; else return 1;
    }
});

请注意,订单返回的符号必须根据您是否希望它按递增或递减排序进行调整(这里,我假设您是从最大到最小排序,所以返回具有较小顺序的那个) .

【讨论】:

  • 在 return 语句之后不需要使用 else。你可以做 if(a.order &gt; b.order) return -1; return 1; 。您还可以删除包装 if(a.name==b.name){} 语句,该语句在执行时始终为真(因为它既不是 &gt; 也不是 &lt;)。
【解决方案4】:

在 ES6 中,如果由于浏览器环境或转译器插件设置而无法保证排序顺序(在理想情况下,这应该不是问题),一种非常惯用/快速的方法可能是:

values
 .map((v, i) => ([v,i]))
 .sort(([v1,i1], [v2,i2])=> (
     (v1==v2) ? (i2-i1) : (v2-v1)
)).map(([v,i])=>v); // or above flipped

这样做的缺点是它的效率不如合并排序,但它非常接近,因为大多数浏览器实现在内部使用它来实现 Array.prototype.sort 甚至更优化(在这种情况下,最终应该只是O(2n) + O(nlogn))。

好处是它非常方便且易于阅读——恕我直言?

【讨论】:

    【解决方案5】:

    由于数组包含引用类型objet,所以可以在sort函数中确定元素的索引。所以我们避免了排序前后的map。

    sortArray.sort((function(a, b) {
      if (a.name < b.name) return -1;
      if (a.name > b.name) return 1;
      return this.indexOf(a) - this.indexOf(b);
    }).bind(sortArray));
    

    【讨论】:

    • 每次都得找索引会不会很慢?
    • 抱歉被否决了:这不行,因为 this.indexOf(a) 和 this.indexOf(b) 在排序时会改变。排序前需要记住初始索引
    • 添加@ionescho,当数组中有多个具有相同值的项目时,indexOf函数也会返回错误的索引。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-01
    • 1970-01-01
    • 2013-03-23
    相关资源
    最近更新 更多