Javascript 如何按多个字段对对象数组进行排序?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6913512/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to sort an array of objects by multiple fields?
提问by Mike
From this original question, how would I apply a sort on multiple fields?
从这个原始问题,我将如何对多个字段应用排序?
Using this slightly adapted structure, how would I sort city (ascending) & then price (descending)?
使用这种稍微调整的结构,我将如何对城市(升序)和价格(降序)进行排序?
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"}
];
I liked the fact than an answerwas given which provided a general approach. Where I plan to use this code, I will have to sort dates as well as other things. The ability to "prime" the object seemed handy, if not a little cumbersome.
我喜欢这个事实,而不是给出了提供一般方法的答案。在我计划使用此代码的地方,我必须对日期和其他内容进行排序。“启动”对象的能力似乎很方便,如果不是有点麻烦的话。
I've tried to build this answerinto a nice generic example, but I'm not having much luck.
我试图将这个答案构建成一个很好的通用示例,但我运气不佳。
采纳答案by Felix Kling
A multi dimensional sorting method, based on this answer:
基于此答案的多维排序方法:
Update: Here is an "optimized" version. It does a lot more preprocessing and creates a comparison function for each sorting option beforehand. It might need more more memory (as it stores a function for each sorting option, but it should preform a bit better as it does not have to determine the correct settings during the comparison. I have not done any profiling though.
更新:这是一个“优化”版本。它做了更多的预处理,并预先为每个排序选项创建了一个比较函数。它可能需要更多内存(因为它为每个排序选项存储一个函数,但它应该执行得更好一些,因为它不必在比较过程中确定正确的设置。虽然我没有做任何分析。
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;
}
}
}());
Example usage:
用法示例:
homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));
Original function:
原函数:
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;
}
};
回答by Snowburnt
for a non-generic, simple solution to your exact problem:
对于您的确切问题的非通用简单解决方案:
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;
});
回答by chriskelly
Here is a simple functional approach. Specify sort order using array. Prepend minusto specify descending order.
这是一个简单的函数式方法。使用数组指定排序顺序。前置减号以指定降序。
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);
};
}
Edit:in ES6 it's even shorter!
编辑:在 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>')
回答by Nina Scholz
You could use a chained sorting approach by taking the delta of values until it reaches a value not equal to zero.
您可以使用链式排序方法,通过取值的增量直到它达到不等于零的值。
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; }
Or, using es6, simply:
或者,使用 es6,只需:
data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);
回答by Teun D
I made a quite generic multi feature sorter today. You can have a look at thenBy.js here: https://github.com/Teun/thenBy.js
我今天做了一个非常通用的多功能分拣机。您可以在此处查看 thenBy.js:https: //github.com/Teun/thenBy.js
It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy() style. It is way less code and complexity than the solutions posted above.
它允许您使用标准的 Array.sort,但使用 firstBy().thenBy().thenBy() 样式。与上面发布的解决方案相比,它的代码和复杂性要少得多。
回答by jake
The following function will allow you to sort an array of objects on one or multiple properties, either ascending (default) or descending on each property, and allow you to choose whether or not to perform case sensitive comparisons. By default, this function performs case insensitive sorts.
以下函数将允许您对一个或多个属性的对象数组进行排序,对每个属性进行升序(默认)或降序,并允许您选择是否执行区分大小写的比较。默认情况下,此函数执行不区分大小写的排序。
The first argument must be the array containing the objects.
The subsequent argument(s) must be a comma separated list of strings that reference the different object properties to sort by. The last argument (which is optional) is a boolean to choose whether or not to perform case sensitive sorts - use true
for case sensitive sorts.
第一个参数必须是包含对象的数组。后续参数必须是逗号分隔的字符串列表,这些字符串引用要作为排序依据的不同对象属性。最后一个参数(可选)是一个布尔值,用于选择是否执行区分大小写的排序 -true
用于区分大小写的排序。
The function will sort each property/key in ascending order by default. If you want a particular key to sort in descending order, then instead pass in an array in this format: ['property_name', true]
.
默认情况下,该函数将按升序对每个属性/键进行排序。如果您希望特定键按降序排序,则改为传入以下格式的数组:['property_name', true]
.
Here are some sample uses of the function followed by an explanation (where homes
is an array containing the objects):
以下是该函数的一些示例用法,然后是解释(其中homes
是包含对象的数组):
objSort(homes, 'city')
--> sort by city (ascending, case in-sensitive)
objSort(homes, 'city')
--> 按城市排序(升序,不区分大小写)
objSort(homes, ['city', true])
--> sort by city (descending, case in-sensitive)
objSort(homes, ['city', true])
--> 按城市排序(降序,不区分大小写)
objSort(homes, 'city', true)
--> sort by city then price (ascending, case sensitive)
objSort(homes, 'city', true)
--> 按城市排序,然后按价格排序(升序,区分大小写)
objSort(homes, 'city', 'price')
--> sort by city then price (both ascending, case in-sensitive)
objSort(homes, 'city', 'price')
--> 按城市排序,然后按价格排序(均升序,不区分大小写)
objSort(homes, 'city', ['price', true])
--> sort by city (ascending) then price (descending), case in-sensitive)
objSort(homes, 'city', ['price', true])
--> 按城市(升序)排序,然后按价格(降序)排序,不区分大小写)
And without further ado, here's the function:
不用多说,这是功能:
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
And here's some sample data:
这是一些示例数据:
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
}];
回答by Guy
This is a complete cheat but I think that it adds value to this question because it's basically a canned library function that you can use out-of-the box.
这是一个完整的作弊,但我认为它为这个问题增加了价值,因为它基本上是一个罐头库函数,您可以开箱即用。
If your code has access to lodash
or a lodash compatible library like underscore
then you can use the _.sortBy
method. The snippet below is copied directly from the lodash documentation.
如果您的代码可以访问lodash
或与 lodash 兼容的库,underscore
那么您可以使用该_.sortBy
方法。下面的代码片段是直接从lodash 文档中复制的。
The commented results in the examples looks like they return arrays of arrays but that's just showing the order and not the actual results which are an array of objects.
示例中的注释结果看起来像是返回数组数组,但这只是显示顺序,而不是作为对象数组的实际结果。
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]]
回答by Flambino
Here's another one that's perhaps closer to your idea for the syntax
这是另一个可能更接近您对语法的想法
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});
Demo: http://jsfiddle.net/Nq4dk/2/
演示:http: //jsfiddle.net/Nq4dk/2/
Edit: Just for fun, here's a variationthat just takes an sql-like string, so you can do sortObjects(homes, "city, price desc")
编辑:只是为了好玩,这里有一个变体,它只需要一个类似 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);
});
}
回答by Ravshan Samandarov
Simpler one:
更简单的一种:
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'}]));
回答by james kenny
I like SnowBurnt's approach but it needs a tweak to test for equivalence on city NOT a difference.
我喜欢 SnowBurnt 的方法,但它需要调整以测试城市的等效性,而不是差异。
homes.sort(
function(a,b){
if (a.city==b.city){
return (b.price-a.price);
} else {
return (a.city-b.city);
}
});