从这个最初的问题,我将如何在多个字段应用排序?
使用这种稍作调整的结构,我将如何排序城市(上升)和价格(下降)?
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"}
];
我喜欢的事实是,给出的答案提供了一个一般的方法。在我计划使用这段代码的地方,我将不得不对日期以及其他东西进行排序。“启动”对象的能力似乎很方便,如果不是有点麻烦的话。
我试图把这个答案构建成一个很好的通用示例,但我运气不太好。
这是一个通用的多维排序,允许在每个层次上进行反转和/或映射。
用Typescript编写。对于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;
};
}
用法示例
先按姓排序,再按名排序:
interface Person {
firstName: string;
lastName: string;
}
people.sort(byObjectValues<Person>(['lastName','firstName']));
按语言代码的名称排序,而不是按语言代码排序(见地图),然后按降序排序(见反向)。
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,
}
]));
我一直在寻找类似的东西,最后得到了这个:
首先,我们有一个或多个排序函数,总是返回0、1或-1:
const sortByTitle = (a, b): number =>
a.title === b.title ? 0 : a.title > b.title ? 1 : -1;
您可以为想要排序的其他属性创建更多函数。
然后我有一个函数将这些排序函数合并为一个:
const createSorter = (...sorters) => (a, b) =>
sorters.reduce(
(d, fn) => (d === 0 ? fn(a, b) : d),
0
);
这可以用来以一种可读的方式组合上述排序函数:
const sorter = createSorter(sortByTitle, sortByYear)
items.sort(sorter)
当一个排序函数返回0时,将调用下一个排序函数进行进一步排序。
这是一个完全的欺骗,但我认为它为这个问题增加了价值,因为它基本上是一个罐装的库函数,你可以开箱即用。
如果你的代码可以访问lodash或者一个与lodash兼容的库,比如下划线,那么你可以使用_。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]]
这是另一个可能更接近您对语法的想法的例子
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(房屋,“城市,价格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);
});
}
下面是我基于施瓦兹变换的解决方案,希望你觉得有用。
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'));