如何通过多个字段对对象数组进行排序?
从这个原始的问题 ,我将如何应用在多个领域的排序?
使用这个稍微适应的结构,我将如何分类城市(上升)和价格(下降)?
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"} ];
我喜欢这个事实,而不是给出了一个总体方法的答案 。 在我打算使用这个代码的地方,我将不得不排序日期以及其他的东西。 如果不是一点点麻烦的话,“引发”对象的能力似乎很方便。
我试图把这个答案建成一个很好的通用例子,但是我没有太多的运气。
基于这个答案的多维排序方法:
更新 :这是一个“优化”的版本。 它做了更多的预处理,并为每个排序选项预先创建一个比较函数。 它可能需要更多的内存(因为它为每个排序选项存储了一个函数,但是它应该会更好一些,因为在比较过程中不需要确定正确的设置),但是我没有做任何分析。
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}));
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; } };
DEMO
对于您的确切问题的非泛型,简单的解决方案:
homes.sort( function(a,b){ if (a.city!=b.city){ return (b.price-a.price); } else { return (a.city-b.city); } });
我今天做了一个非常通用的多功能分类器。 你可以在这里看看thenBy.js: https : //github.com/Teun/thenBy.js
它允许你使用标准的Array.sort,但是使用firstBy()。thenBy()。thenBy()风格。 与上面所提出的解决方案相比,它的代码和复杂性要少得多
这是一个简单的功能方法。 使用数组指定排序顺序。 预先减去指定降序。
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"; function fieldSorter(fields) { return (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(['zip', '-city'])); document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')
以下函数将允许您对一个或多个属性上的对象数组进行排序,每个属性可以升序(默认)或降序,并允许您选择是否执行区分大小写的比较。 默认情况下,这个函数执行不区分大小写的排序。
第一个参数必须是包含对象的数组。 随后的参数必须是逗号分隔的字符串列表,引用不同的对象属性进行排序。 最后一个参数(可选)是一个布尔值,用于选择是否执行区分大小写的排序 – 对区分大小写排序使用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
这里有一些示例数据:
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 }];
这里有另一个可能更接近你的语法思想
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});
演示: 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); }); }
更简单的一个:
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'}]));
这是一个完整的作弊,但我认为它增加了这个问题的价值,因为它基本上是一个罐头库功能,你可以使用开箱即用。
如果您的代码可以访问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]]
以下是@ Snowburnt解决方案的通用版本:
var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}]; array.sort(function(a,b){ for(var i=0; i<sortarray.length; i++){ retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0; if (sortarray[i].direction == "desc") { retval = retval * -1; } if (retval !== 0) { return retval; } } } })
这是基于我正在使用的排序例程。 我没有测试这个特定的代码,所以它可能有错误,但你明白了。 这个想法是根据表示差异的第一个字段进行排序,然后停止并转到下一个记录。 因此,如果您按三个字段进行排序,并且比较中的第一个字段足以确定正在排序的两个记录的排序顺序,则返回该排序结果并转到下一个记录。
我在5000条记录上测试了它(实际上有一些更复杂的排序逻辑),并且在一眨眼的时候就完成了。 如果您实际上将超过1000条记录加载到客户端,则应该使用服务器端分类和过滤。
此代码不处理区分大小写,但我把它留给读者来处理这个微不足道的修改。
这里是我的基于Schwartzian变换成语的解决方案,希望你觉得它有用。
function sortByAttribute(array, ...attrs) { // generate an array of predicate-objects contains // property getter, and descending indicator let predicates = attrs.map(pred => { let descending = pred.charAt(0) === '-' ? -1 : 1; pred = pred.replace(/^-/, ''); return { getter: o => o[pred], descend: descending }; }); // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate" return array.map(item => { return { src: item, compareValues: predicates.map(predicate => predicate.getter(item)) }; }) .sort((o1, o2) => { let i = -1, result = 0; while (++i < predicates.length) { if (o1.compareValues[i] < o2.compareValues[i]) result = -1; if (o1.compareValues[i] > o2.compareValues[i]) result = 1; if (result *= predicates[i].descend) break; } return result; }) .map(item => item.src); }
下面是一个如何使用它的例子:
let games = [ { name: 'Pako', rating: 4.21 }, { name: 'Hill Climb Racing', rating: 3.88 }, { name: 'Angry Birds Space', rating: 3.88 }, { name: 'Badland', rating: 4.33 } ]; // sort by one attribute console.log(sortByAttribute(games, 'name')); // sort by mupltiple attributes console.log(sortByAttribute(games, '-rating', 'name'));
function sortMultiFields(prop){ return function(a,b){ for(i=0;i<prop.length;i++) { var reg = /^\d+$/; var x=1; var field1=prop[i]; if(prop[i].indexOf("-")==0) { field1=prop[i].substr(1,prop[i].length); x=-x; } if(reg.test(a[field1])) { a[field1]=parseFloat(a[field1]); b[field1]=parseFloat(b[field1]); } if( a[field1] > b[field1]) return x; else if(a[field1] < b[field1]) return -x; } } }
如何使用 (如果你想按降序排列特定字段,请在字段前加上 – (减号))
homes.sort(sortMultiFields(["city","-price"]));
使用上面的函数,你可以排序任何具有多个字段的JSON数组。 根本不需要改变功能体
我喜欢SnowBurnt的方法,但它需要一个调整来测试城市的等效性不是一个区别。
homes.sort( function(a,b){ if (a.city==b.city){ return (b.price-a.price); } else { return (a.city-b.city); } });
其他方式
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"} ]; function sortBy(ar) { return ar.sort((a, b) => a.city === b.city ? b.price.toString().localeCompare(a.price) : a.city.toString().localeCompare(b.city)); } console.log(sortBy(homes));
homes.sort(function(a,b) { return a.city - b.city } ); homes.sort(function(a,b){ if (a.city==b.city){ return parseFloat(b.price) - parseFloat(a.price); } else { return 0; } });
这里'AffiliateDueDate'和'Title'是列,都是按升序排列的。
array.sort(function(a, b) { if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1; else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1; else if (a.Title > b.Title ) return 1; else if (a.Title < b.Title ) return -1; else return 0; })
排序在两个日期字段和一个数字字段示例:
var generic_date = new Date(2070, 1, 1); checkDate = function(date) { return Date.parse(date) ? new Date(date): generic_date; } function sortData() { data.sort(function(a,b){ var deltaEnd = checkDate(b.end) - checkDate(a.end); if(deltaEnd) return deltaEnd; var deltaRank = a.rank - b.rank; if (deltaRank) return deltaRank; var deltaStart = checkDate(b.start) - checkDate(a.start); if(deltaStart) return deltaStart; return 0; }); }
function sort(data, orderBy) { orderBy = Array.isArray(orderBy) ? orderBy : [orderBy]; return data.sort((a, b) => { for (let i = 0, size = orderBy.length; i < size; i++) { const key = Object.keys(orderBy[i])[0], o = orderBy[i][key], valueA = a[key], valueB = b[key]; if (!(valueA || valueB)) { console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!"); return []; } if (+valueA === +valueA) { return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB; } else { if (valueA.localeCompare(valueB) > 0) { return o.toLowerCase() === 'desc' ? -1 : 1; } else if (valueA.localeCompare(valueB) < 0) { return o.toLowerCase() === 'desc' ? 1 : -1; } } } }); }
使用:
sort(homes, [{city : 'asc'}, {price: 'desc'}])
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"} ]; function sort(data, orderBy) { orderBy = Array.isArray(orderBy) ? orderBy : [orderBy]; return data.sort((a, b) => { for (let i = 0, size = orderBy.length; i < size; i++) { const key = Object.keys(orderBy[i])[0], o = orderBy[i][key], valueA = a[key], valueB = b[key]; if (!(valueA || valueB)) { console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!"); return []; } if (+valueA === +valueA) { return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB; } else { if (valueA.localeCompare(valueB) > 0) { return o.toLowerCase() === 'desc' ? -1 : 1; } else if (valueA.localeCompare(valueB) < 0) { return o.toLowerCase() === 'desc' ? 1 : -1; } } } }); } console.log(sort(homes, [{city : 'asc'}, {price: 'desc'}]));
您可以使用链接排序方法,通过取值的增量直到达到不等于零的值。
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; }