Python3 sort 自定义比较逻辑:lambda函数、可比较类、cmp_to_key
以字符串排序为例,现在我们有这样一个字符串的 list :
words = ["vanilla", "tom", "sue", "david", "bill", "tom", "jezebel", "jackb", "jack", "jacka"]
默认排序方式
对于字符串而言,其默认的比较大小逻辑为:按首个字符顺序比较大小,相同则看后一个字符,后一个字符为空者较小。
def test1(things):
things.sort()
print(things)
test1(words.copy())
输出:
['bill', 'david', 'jack', 'jacka', 'jackb', 'jezebel', 'sue', 'tom', 'tom', 'vanilla']
lambda函数
sort
方法可以接收
key
参数,传递一个如下的可调用的对象:
以下例子通过 lambda 函数设置新的排序逻辑:以字符串长度为基准。
lambda 函数需要:1.接收1个参数;2.返回一个值,且返回值是可比较大小的。
def test2(things):
things.sort(key=lambda e: len(e))
print(things)
test2(words.copy())
输出:
['tom', 'sue', 'tom', 'bill', 'jack', 'david', 'jackb', 'jacka', 'vanilla', 'jezebel']
自定义可比较类
但是 lambda 函数无法表示比较复杂的排序逻辑。比如我们想:优先按照字符串长度为基准排序,再按照字母大小排序。lambda 函数是做不到如此复杂的逻辑的。
实际上,
key
参数也可以传接收1个参数以初始化的类,比如下面的继承
str
类的
Cmp
类(因为
str
本身就接收一个参数以初始化,所以无需重写
Cmp
的
__init__
函数)
为了使
Cmp
是可比较大小的,需定义用于比较的 magic method,这些函数为:
__gt__
,
__lt__
,
__ge__
,
__le__
,
__eq__
,分别在进行 >, =, <=, == 运算时被调用,一个鲁棒的可比较类需要包含全部这五个函数的定义。
Cmp
中只重写了
__lt__
,因为在 sort 的执行中,优先进行 < 运算(只有在 < 运算抛出异常的情况下,才会试图进行其它比较运算)
def test3(things):
class Cmp(str):
def __lt__(self, other):
if len(self) == len(other):
return str.__lt__(self, other)
else:
return len(self) < len(other)
things.sort(key=Cmp)
print(things)
test3(words.copy())
输出:
['sue', 'tom', 'tom', 'bill', 'jack', 'david', 'jacka', 'jackb', 'jezebel', 'vanilla']
通过cmp_to_key自定义比较逻辑
定义鲁棒的
Cmp
类比较麻烦,毕竟两个对象的大小比较,无外乎“大于”、“小于”、“等于”三个状态。最直接的方法难道不是定义一个函数,输入两个参数,输出前者与后者之间的大小关系吗?可是有这样的接口吗?有的。
通过
functools
模块里的
cmp_to_key
函数,可以简洁地将上述自定义函数转化成
key
可接收的格式。
自定义函数需要:1.接收两个参数
p1
,
p2
;2.返回1、0或-1,其中1代表
p1 > p2
,0代表
p1 == p2
, -1代表
p1 < p2
。
def test4(things):
def compare(s1, s2):
if len(s1) == len(s2):
for c1, c2 in zip(s1, s2):
if c1 > c2:
return 1
elif c1 < c2:
return -1
return 0 # 这里的比较其实可以直接使用字符串之间的 >,
else:
if len(s1) > len(s2):
return 1
else:
return -1
from functools import cmp_to_key
things.sort(key=cmp_to_key(compare))
print(things)
test4(words.copy())
输出:
['sue', 'tom', 'tom', 'bill', 'jack', 'david', 'jacka', 'jackb', 'jezebel', 'vanilla']