列表按多字段排序的需求,极常见。
回顾一下sort方法中,compare方法参数的基本规则(不同语言都差不多,这里拿js中ab对象的x字段来说):
若想要升序排序,可直接返回 a.x - b.x 。原理:比较时若a.x>b.x,则差为正,交换a和b的位置;反之若a.x<b.x,则差为负,保持a和b的位置不变。
若想要降序排序,就直接返回 b.x - a.x。
通常,对number类型进行比较排序非常容易,直接做减法即可。如,先按 "num降序排序" 再按 "id升序"排序:
list.sort((a, b) => {
if (a.num != b.num) {
return b.num - a.num;
}
return a.id - b.id;
});
那对不能直接比较大小的 bool 值,要怎么排序呢?
list.sort((a, b) => {
if (a.bool != b.bool) {
return a.bool ? 1 : -1;
}
return a.id - b.id;
});
这样没问题(bool 数据只有两种分组 true 和 false,还比较容易)。
那分组情况较多的 string 类型数据要怎么处理呢?强行写 if else 列出每种情况非常麻烦。
实际上,对于非 number 的数据类型,都可以 创建临时的排序映射 来解决排序问题。如下:
(sting 和 enum 是类似的)
//name,按:粮食 > 铁矿 > 石头 > 木头
let nameSortMap = { "粮食" : 1, "铁矿" : 2, "石头" : 3, "木头" : 4 };
//received,按:true > false
let receivedSortMap = { "true" : 1, "false" : 2 };
rewardList.sort((a, b) => {
//先按领取状态
if (a.hasReceived != b.hasReceived) {
return receivedSortMap[a.hasReceived.toString()] - receivedSortMap[b.hasReceived.toString()];
} else {
//再按道具类型
if (a.name != b.name) {
return nameSortMap[a.name] - nameSortMap[b.name];
} else {
//再按数量 降序
if (a.num != b.num) {
return b.num - a.num;
} else {
//最后按id 升序
return a.id - b.id;
}
}
}
});
结果如下:
问题到此算是解决了。那是否还可以继续优化呢?
实际上,在范围可控的情况下,可以为每个字段赋予一个权重,各字段分别乘以各自的权重,最终得到一个值,对此值进行排序即可。
这样可以消除嵌套的 if else。注意,它只适用于范围可控的情况。