原生Python如何实现按str升序、datetime降序的多字段排序
待排序的列表元素包含两个属性:
i.member : str i.time : datetime
最初尝试的排序代码:
l.sort(key=lambda i: (i.member, -i.time))
运行报错,原因是Python不支持直接用-运算符对datetime对象取反。需求是实现和Pandasdf.sort_values(by=['member', 'time'], ascending=[True, False])完全一致的效果:先按member字段升序排列,同一个member下的记录按time字段降序排列,且不引入第三方库带来的额外加载开销。
不需要依赖任何第三方库,有两种成熟方案可选:
方案1:借助稳定排序特性分两次排序(通用首选)
Python内置的列表排序是稳定排序:如果两个元素的排序key相等,它们在排序后列表中的相对顺序和排序前完全一致。利用这个特性,我们可以按排序优先级从低到高依次排序,高优先级的排序不会打乱低优先级同key元素的已有顺序。
对应代码非常简单:
# 先排优先级更低的time字段,指定reverse=True实现降序 l.sort(key=lambda i: i.time, reverse=True) # 再排优先级更高的member字段,默认升序即可 # 稳定排序特性会自动保留同一个member下,之前排好的time降序结果 l.sort(key=lambda i: i.member)
这个方案没有任何类型限制,不管字段是datetime、字符串还是自定义可比较类型都能正常工作,不需要对字段值做额外转换,兼容性拉满。两次排序的总时间复杂度依然是O(n log n),和单次排序的性能差异可以忽略。
方案2:转换key值实现单次元组排序
如果希望只调用一次sort完成排序,可以把需要降序的字段转换成支持反向比较的值。针对datetime类型,直接取对应的Unix时间戳,对时间戳取反就能实现倒序效果:
l.sort(key=lambda i: (i.member, -i.time.timestamp()))
使用这个方法需要注意:列表里所有的datetime对象必须是同时区的(要么都是不带时区信息的naive时间,要么都是同一时区的aware时间),否则时间戳转换会出现逻辑偏差。如果是字符串这类无法直接取反的字段,需要额外实现反向比较的包装类,通用性不如两次排序的方案。
说明:Python原生sort不支持直接传入布尔列表为每个key字段单独指定升降序。因为sort的key参数只要求返回一个可比较对象,元组作为key时是按位置逐位比较大小的,本身没有为每个位置单独指定排序方向的设计,所以要么转换字段值适配元组比较规则,要么利用稳定排序分多轮完成。
内容的提问来源于stack exchange,提问作者ifly6

