如何按多个字段对对象数组进行排序?

IT技术 javascript arrays sorting
2021-01-28 23:13:39

从这个原始问题,我将如何对多个字段应用排序?

使用这种稍微调整的结构,我将如何对城市(升序)和价格(降序)进行排序?

var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

我喜欢这个事实,而不是给出了提供一般方法答案在我计划使用此代码的地方,我必须对日期和其他内容进行排序。“准备”对象的能力似乎很方便,如果不是有点麻烦的话。

我试图将这个答案构建成一个很好的通用示例,但我运气不佳。

6个回答

对于您的确切问题的非通用简单解决方案:

homes.sort(
   function(a, b) {          
      if (a.city === b.city) {
         // Price is only important when cities are the same
         return b.price - a.price;
      }
      return a.city > b.city ? 1 : -1;
   });
十分优雅。如果 javascript 有一个 sortBy 和一个像 LINQ 这样的 thenSortBy,那就太好了。
2021-03-09 23:13:39
我认为这个演示是 OP 想要的 => jsfiddle.net/zJ6UA/533
2021-03-10 23:13:39
第一个城市比较不应该检查平等,而不是不平等吗?换句话说,这条线不应该是if (a.city === b.city)吗?也就是说,如果两个城市相同,则比较价格,否则比较城市。
2021-03-21 23:13:39
您可以a.localeCompare(b)在最后一行中使用字符串比较...请参阅文档
2021-04-03 23:13:39
这个想法是对的,但是逻辑全错了。您不能从另一个字符串中减去一个非数字字符串,并且该if语句没有任何意义。
2021-04-06 23:13:39

您可以使用链式排序方法,取值的增量,直到它达到不等于零的值。

var data = [{ h_id: "3", city: "Dallas", state: "TX", zip: "75201", price: "162500" }, { h_id: "4", city: "Bevery Hills", state: "CA", zip: "90210", price: "319250" }, { h_id: "6", city: "Dallas", state: "TX", zip: "75000", price: "556699" }, { h_id: "5", city: "New York", state: "NY", zip: "00010", price: "962500" }];

data.sort(function (a, b) {
    return a.city.localeCompare(b.city) || b.price - a.price;
});

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

或者,使用 es6,只需:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);
@AndyLorenz 完全同意。解决这个问题的方法很多。例如,当超过 y 个答案高于 z 评分时,用户设置最小化超过 x 年的答案。一个更简单的开始是向排序按钮添加“最新”选项。
2021-03-20 23:13:39
我错过了什么吗?为什么要用 60 行代码来做可以在 1 中完成的事情。简单、清晰、简洁。应该是 IMO 接受的答案。
2021-03-22 23:13:39
SO 现在的一个大问题是旧答案 - 通常被使用新语言功能(例如 ES5-6-7)的更好解决方案所取代,保持其旧分数,我们都必须向下滚动以找到“真正”最好的解决方案!SO 应该随着时间的推移使投票过期来解决这个问题,因为随着时间的推移,问题变得越来越严重。
2021-03-31 23:13:39
这是一个很好的答案 - 非常简洁!也许值得解释一下它的工作原理是因为零 - 当两个值匹配时 localeCompare() 返回 - 是假的,而 -1 和 +1 是真的。
2021-04-03 23:13:39
结合另一个满足我需求的答案 - 多个属性的 a - z : return a.status.localeCompare(b.status) || a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
2021-04-06 23:13:39

基于此答案的多维排序方法

更新:这是一个“优化”版本。它做了更多的预处理,并预先为每个排序选项创建了一个比较函数。它可能需要更多内存(因为它为每个排序选项存储一个函数,但它应该执行得更好一些,因为它不必在比较期间确定正确的设置。不过我还没有做任何分析。

var sort_by;

(function() {
    // utility functions
    var default_cmp = function(a, b) {
            if (a == b) return 0;
            return a < b ? -1 : 1;
        },
        getCmpFunc = function(primer, reverse) {
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) {
                cmp = function(a, b) {
                    return dfc(primer(a), primer(b));
                };
            }
            if (reverse) {
                return function(a, b) {
                    return -1 * cmp(a, b);
                };
            }
            return cmp;
        };

    // actual implementation
    sort_by = function() {
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) {
            field = arguments[i];
            if (typeof field === 'string') {
                name = field;
                cmp = default_cmp;
            }
            else {
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            }
            fields.push({
                name: name,
                cmp: cmp
            });
        }

        // final comparison function
        return function(A, B) {
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) {
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            }
            return result;
        }
    }
}());

用法示例:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));

演示


原函数:

var sort_by = function() {
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) {
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) {
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined'){
               a = field.primer(a);
               b = field.primer(b);
           }

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       }
       return result;
   }
};

演示

@Mike:好的...终于 ;) 你看到它现在更复杂,因为选项是预处理的,但最终的比较函数(见评论)更简单(希望)导致更好的性能。您拥有的排序选项越多,这种方法的优势就越大。
2021-03-22 23:13:39
为了记录,这个函数仍然可以通过预处理参数列表并创建一个统一的“排序选项数组”来改进。这留给读者作为练习;)
2021-03-23 23:13:39

这是一个简单的功能通用方法。使用数组指定排序顺序。前置减号以指定降序。

var homes = [
    {"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":"162500"},
    {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"},
    {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"},
    {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"}
    ];

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
    return function (a, b) {
        return fields
            .map(function (o) {
                var dir = 1;
                if (o[0] === '-') {
                   dir = -1;
                   o=o.substring(1);
                }
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            })
            .reduce(function firstNonZeroValue (p,n) {
                return p ? p : n;
            }, 0);
    };
}

编辑:在 ES6 中它更短!

"use strict";
const fieldSorter = (fields) => (a, b) => fields.map(o => {
    let dir = 1;
    if (o[0] === '-') { dir = -1; o=o.substring(1); }
    return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0;
}).reduce((p, n) => p ? p : n, 0);

const homes = [{"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":162500},     {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":319250},{"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":556699},{"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":962500}];
const sortedHomes = homes.sort(fieldSorter(['state', '-price']));

document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')

我发现这个函数非常简洁,因此根据解析器的不同,我对性能进行了高达 90% 的小幅提升。我做了一个要点测试套件
2021-03-09 23:13:39
@MarkCarpenterJr。刚发现。我在评论中添加了解释。
2021-03-10 23:13:39
@MarkCarpenterJr。不明白你的意思。我的示例正确排序数字类型。您能否将您的实现作为一个问题分享并在评论中引用我以便我看到它?那我可以查一下。
2021-03-30 23:13:39
基于它看起来像数字样本数据进行排序预期,但是当我试图在那里排序更像字符串实现这个号码...... [10,100,11,9]我错过了什么?
2021-04-05 23:13:39

我今天做了一个非常通用的多功能分拣机。您可以在此处查看 thenBy.js:https : //github.com/Teun/thenBy.js

它允许您使用标准的 Array.sort,但使用 firstBy().thenBy().thenBy() 样式。与上面发布的解决方案相比,它的代码和复杂性要少得多。

好吧,当您调用 3 次时,对于第二次调用没有影响的项目,不能保证第二次调用不会改变第一个调用的顺序。
2021-03-31 23:13:39