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

Mik*_*ike 113 javascript arrays sorting

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

使用这种略微适应的结构,我如何排序城市(升序)然后价格(降序)?

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

我喜欢这个事实而不是给出一个通用方法的答案.在我计划使用此代码的地方,我将不得不对日期以及其他内容进行排序."引导"物体的能力似乎很方便,如果不是有点麻烦.

我试图将这个答案构建成一个很好的通用示例,但我没有太多运气.

Sno*_*rnt 115

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

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

  • 我认为这个演示是OP想要的=> http://jsfiddle.net/zJ6UA/533/ (6认同)
  • 您可以在字符串比较的最后一行使用`a.localeCompare(b)`[参见文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Global_Objects /字符串/ localeCompare) (4认同)
  • 这有正确的想法,但逻辑是错的.你不能从另一个字符串中减去非数字字符串,而`if`语句没有任何意义. (3认同)
  • 第一个城市比较不应该检查平等,而不是不平等吗?换句话说,该行不应该是`if(a.city === b.city)`?也就是说,如果两个城市相同,则比较价格,否则比较城市. (2认同)
  • 十分优雅。如果 javascript 有一个 sortBy 和一个像 LINQ 一样的 thenSortBy,那就太好了。 (2认同)

Fel*_*ing 76

基于这个答案的多维排序方法:

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

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

用法示例:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));
Run Code Online (Sandbox Code Playgroud)

DEMO


原功能:

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

DEMO

  • 为了记录,这个函数仍然可以通过预处理参数列表并创建一个统一的“排序选项数组”来改进。这留给读者作为练习;) (3认同)

chr*_*lly 44

这是一个简单的功能方法.使用数组指定排序顺序.前面减去指定降序.

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

编辑:在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>')
Run Code Online (Sandbox Code Playgroud)

  • 我发现此功能非常简洁,因此根据解析器的不同,我的性能提高了90%。我做了一个[要点](https://gist.github.com/manix/a2160e7084c5c7fef5431af359fb342e)和[测试套件](https://jsperf.com/fieldsorter)。 (2认同)

Teu*_*n D 32

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

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

  • 好吧,当你拨打3次电话时,第二次通话不能保证第二次通话的顺序不会受到第二次通话无效的项目的影响. (8认同)

Nin*_*olz 16

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

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);
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Run Code Online (Sandbox Code Playgroud)

或者,使用es6,只需:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);
Run Code Online (Sandbox Code Playgroud)

  • SO 现在的大问题之一是旧答案 - 通常会被使用新语言功能(例如 ES5-6-7)的更好解决方案所取代,但仍保持其旧分数,我们都必须向下滚动才能找到“真正的”最佳答案解决方案!SO 应该随着时间的推移终止投票来解决这个问题,因为随着时间的推移,问题会变得越来越严重。 (14认同)
  • 我想念什么吗?为什么要使用60行代码完成可以在1中完成的工作。简单,清晰,简洁。应该是IMO的公认答案。 (2认同)
  • @GarrodRan,切换 a 和 b。 (2认同)
  • 这是一个很好的答案 - 非常简洁!也许值得解释一下它的工作原理,因为当两个值匹配时 localeCompare() 返回的零是假的,而 -1 和 +1 是真值。 (2认同)

jak*_*ake 12

以下函数将允许您对一个或多个属性上的对象数组进行排序,可以是升序(默认),也可以是降序每个属性,并允许您选择是否执行区分大小写的比较.默认情况下,此函数执行不区分大小写的排序.

第一个参数必须是包含对象的数组.后续参数必须是以逗号分隔的字符串列表,这些字符串引用要排序的不同对象属性.最后一个参数(可选)是一个布尔值,用于选择是否执行区分大小写的排序 - true用于区分大小写的排序.

该函数默认按升序对每个属性/键进行排序.如果您希望特定键按降序排序,则以这种格式传入数组:['property_name', true].

下面是函数的一些示例用法,后面是解释(其中homes是包含对象的数组):

objSort(homes, 'city') - >按城市排序(升序,不区分大小写)

objSort(homes, ['city', true]) - >按城市排序(降序,不区分大小写)

objSort(homes, 'city', true)- >按城市排序然后定价(升序,区分大小写)

objSort(homes, 'city', 'price') - >按城市排序然后定价(升序,案例不敏感)

objSort(homes, 'city', ['price', true]) - >按城市排序(升序)然后按价格(降序),案例不敏感)

没有进一步的麻烦,这里的功能:

function objSort() {
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') {
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
    } else {
        case_sensitive = false;
        keys_length = arguments.length;
    }

    return array.sort(function (obj1, obj2) {
        for (i = 1; i < keys_length; i++) {
            key = args[i];
            if (typeof key !== 'string') {
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
            } else {
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            }

            if (case_sensitive === false && typeof a === 'string') {
                a = a.toLowerCase();
                b = b.toLowerCase();
            }

            if (! desc) {
                if (a < b) return -1;
                if (a > b) return 1;
            } else {
                if (a > b) return -1;
                if (a < b) return 1;
            }
        }
        return 0;
    });
} //end of objSort() function
Run Code Online (Sandbox Code Playgroud)

以下是一些示例数据:

var homes = [{
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
}, {
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
}, {
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
}, {
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
}, {
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
}];
Run Code Online (Sandbox Code Playgroud)


Fla*_*ino 7

这是另一个可能更接近你的语法想法

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {}; // primers are optional

    properties = properties.map(function(prop) {
        if( !(prop instanceof Array) ) {
            prop = [prop, 'asc']
        }
        if( prop[1].toLowerCase() == 'desc' ) {
            prop[1] = -1;
        } else {
            prop[1] = 1;
        }
        return prop;
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
    return str.split('').reverse().join('');
}

// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});
Run Code Online (Sandbox Code Playgroud)

演示:http://jsfiddle.net/Nq4dk/2/


编辑:只是为了好玩,这里的变体只需要一个类似sql的字符串,所以你可以这样做sortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {};

    properties = properties.split(/\s*,\s*/).map(function(prop) {
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
            return [prop[1] , -1];
        } else {
            return [prop[1] , 1];
        }
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}
Run Code Online (Sandbox Code Playgroud)


Rav*_*rov 7

更简单的一个:

var someArray = [...];

function generateSortFn(props) {
    return function (a, b) {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        }
        return 0;
    };
};

someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));
Run Code Online (Sandbox Code Playgroud)


Guy*_*Guy 7

这是一个完整的作弊,但我认为它增加了这个问题的价值,因为它基本上是一个罐头库函数,你可以使用开箱即用.

如果您的代码可以访问lodash或者像lodash兼容的库,underscore那么您可以使用该_.sortBy方法.下面的代码片段直接从lodash文档中复制.

示例中的注释结果看起来像是返回数组的数组,但这只是显示顺序而不是实际结果,它们是一个对象数组.

var users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
Run Code Online (Sandbox Code Playgroud)


Jos*_*sen 7

这是一个通用的多维排序,允许在每个级别上进行反转和/或映射。

用打字稿写的。对于 Javascript,请查看此JSFiddle

编码

type itemMap = (n: any) => any;

interface SortConfig<T> {
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;
}

export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 {
  return function(a: T, b: T) {
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) {
      if (keys.length === 1) {
        return 0;
      }
      return byObjectValues<T>(keys.slice(1))(a, b);
    }
    if (reverse) {
      return valA > valB ? -1 : 1;
    }
    return valA > valB ? 1 : -1;
  };
}
Run Code Online (Sandbox Code Playgroud)

使用示例

按姓氏排序 people 数组,然后按名字排序:

interface Person {
  firstName: string;
  lastName: string;
}

people.sort(byObjectValues<Person>(['lastName','firstName']));
Run Code Online (Sandbox Code Playgroud)

名称排序语言代码,而不是按语言代码(参见map),然后按降序排序(参见reverse)。

interface Language {
  code: string;
  version: number;
}

// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  {
    key: 'code',
    map(code:string) => languageCodeToName(code),
  },
  {
    key: 'version',
    reverse: true,
  }
]));
Run Code Online (Sandbox Code Playgroud)


Bob*_*ein 6

这是按多个字段排序的可扩展方法。

homes.sort(function(left, right) {
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
});
Run Code Online (Sandbox Code Playgroud)

笔记

  • 传递给数组 sort 的函数应返回负数、零、正数以表示较小、相等、较大。
  • a.localeCompare(b)普遍支持的字符串,并返回-1,0,1如果a<ba==ba>b
  • 减法适用于数字字段,因为a - b给出 -,0,+ if a<b, a==b, a>b
  • ||在最后一行中city优先于price.
  • 否定在任何字段中反转顺序,如 -price_order
  • 向 or 链添加新字段: return city_order || -price_order || date_order;
  • 日期与减法比较,因为日期数学转换为自 1970 年以来的毫秒数。
    var date_order = new Date(left.date) - new Date(right.date);
  • 布尔比较与减法,保证将真假变为 1 和 0(因此减法产生 -1 或 0 或 1)。
    var goodness_order = Boolean(left.is_good) - Boolean(right.is_good)
    这很不寻常,我建议使用 Boolean 构造函数引起注意,即使它们已经是布尔值。


Leo*_*ipe 6

使用 MULTIPLE 键执行此操作的动态方法:

  • 从排序的每个列/键中过滤唯一值
  • 整理或颠倒它
  • 根据 indexOf(value) 键值为每个对象添加权重宽度 zeropad
  • 使用计算权重排序

在此处输入图片说明

Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) { 
    sorts.map(sort => {            
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );
        
        sort.uniques = sort.uniques.sort((a, b) => {
            if (typeof a == 'string') {
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            }
            else if (typeof a == 'number') {
                return sort.inverse ? b - a : a - b;
            }
            else if (typeof a == 'boolean') {
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            }
            return 0;
        });
    });

    const weightOfObject = (obj) => {
        let weight = "";
        sorts.map(sort => {
            let zeropad = `${sort.uniques.length}`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        });
        //obj.weight = weight; // if you need to see weights
        return weight;
    }

    this.sort((a, b) => {
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    });
    
    return this;
}
});
Run Code Online (Sandbox Code Playgroud)

用:

// works with string, number and boolean
let sortered = your_array.orderBy([
    {key: "type", inverse: false}, 
    {key: "title", inverse: false},
    {key: "spot", inverse: false},
    {key: "internal", inverse: true}
]);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明


小智 6

为什么要复杂化?只需排序两次即可!这非常有效:(只需确保将重要性顺序从最低到最高颠倒即可):

jj.sort( (a, b) => (a.id >= b.id) ? 1 : -1 );
jj.sort( (a, b) => (a.status >= b.status) ? 1 : -1 );
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

117923 次

最近记录:

6 年,2 月 前