用sort/sorted方法的key参数来表示复杂的排序逻辑
01. 简介
sort()
是应用在内置的列表类型(list)上的方法,主要有以下特点:
- 这个方法会修改原始的 list(返回值为None)
- 通常这个方法不如sorted()方便
- 如果你不需要原始的 list,list.sort()方法效率会稍微高一些。
02. 用法
2.1 基本用法
>>> a = [3,6,1,8,0,5,7,9,2,4]
>>> a.sort()
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2.2 列表的内容是一些复杂的元素
列表元素是元组
student = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10),
]
# 按照元组的第三个元素排序
# 默认是按照从小到大排序,即reverse为False,如果设置为True,则从大到小排序
student.sort(key=lambda x: x[2], reverse=False)
print(student)
# 输出结果
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
列表元素是类
class Student:
def __init__(self, name, grade, age):
self.name = name
self.grade = grade
self.age = age
def __repr__(self):
return repr((self.name, self.grade, self.age))
students = [
Student('john', 'A', 15),
Student('jane', 'B', 12),
Student('dave', 'B', 10),
]
# 根据年龄排序
students.sort(key=lambda s: s.age)
print(students)
# 输出结果
[name is dave, B, 10, name is jane, B, 12, name is john, A, 15]
列表元素是字典
student = [
{"name": "xiaoming", "score": 60},
{"name": "daxiong", "score": 20},
{"name": "maodou", "score": 30},
]
student.sort(key=lambda d: d['score'])
print(student)
# 输出结果
[{'name': 'daxiong', 'score': 20}, {'name': 'maodou', 'score': 30}, {'name': 'xiaoming', 'score': 60}]
2.3 利用itemgetter 与 attrgetter实现排序
- itemgetter 是以index的形势来获取相对应的值。
- attrgetter是用 key来获取相对应的值
from operator import attrgetter
stu = [
("A", 30),
("B", 20),
("C", 10),
("A", 40)
]
stu.sort(key=itemgetter(1))
print(stu)
# operator提供了多个字段的复杂排序,先对第0个字段排序,再对第一个字段排序
stu.sort(key=itemgetter(0, 1))
print(stu)
# 输出结果
[('C', 10), ('B', 20), ('A', 30), ('A', 40)]
[('A', 30), ('A', 40), ('B', 20), ('C', 10)]
from operator import attrgetter
class Student:
def __init__(self, name, grade, age):
self.name = name
self.grade = grade
self.age = age
def __repr__(self):
return repr((self.name, self.grade, self.age))
stu = [
Student('jane', 'B', 12),
Student('john', 'A', 11),
Student('dave', 'B', 10),
Student('maye', 'A', 9),
]
# 对students按照年龄排序, 其等价于 stu.sort(key=lambda o: o.age)
stu.sort(key=attrgetter('age'))
print(stu)
# 也可以按多个key排序, 先按age再按grade排序
stu.sort(key=attrgetter('grade', 'age'))
print(stu)
# 输出结果
[('maye', 'A', 9), ('dave', 'B', 10), ('john', 'A', 11), ('jane', 'B', 12)]
[('maye', 'A', 9), ('john', 'A', 11), ('dave', 'B', 10), ('jane', 'B', 12)]
2.4 多个排序标准
原理:两个元组是可以比较的。因为这种类型本身已经定义了自然顺序,也就是说,一些特殊方法(例如__lt__方法)。元组在实现这些特殊方法时会一次比较每个位置的两个对应元素,直到能够确定大小为止
class Student:
def __init__(self, name, grade, age):
self.name = name
self.grade = grade
self.age = age
def __repr__(self):
return repr((self.name, self.grade, self.age))
stu = [
Student('jane', 'B', 12),
Student('john', 'A', 11),
Student('dave', 'B', 10),
Student('maye', 'A', 9),
]
stu.sort(key=lambda x: (x.grade, x.age))
# 也可以采用attrgetter来进行排序 stu.sort(key=attrgetter('grade', 'age'))
print(stu)
# 输出结果
[('maye', 'A', 9), ('john', 'A', 11), ('dave', 'B', 10), ('jane', 'B', 12)]
注意:这样排序存在一个缺点,就是key函数所构造的这个元组只能按照同一个排序方向对比,即要么都是升序,要么都是降序
解决办法: 针对数字指标,可以利用减操作符来按照不同的方向排序,例如
stu = [
Student('jane', 'B', 12),
Student('john', 'A', 11),
Student('dave', 'B', 10),
Student('maye', 'A', 9),
]
stu.sort(key=lambda x: (x.grade, -x.age))
print(stu)
# 输出结果
[('john', 'A', 11), ('maye', 'A', 9), ('jane', 'B', 12), ('dave', 'B', 10)]
但是这种解决办法对于非数字型的参数是无法操作了,所以可以采用以下的方法,注意优先排序不重要的指标,即如果要求先按grade排序,再按age排序,那么要先排序一次age,再排grade
stu = [
Student('jane', 'B', 12),
Student('john', 'A', 11),
Student('dave', 'B', 10),
Student('maye', 'A', 9),
]
stu.sort(key=lambda x: x.age, reverse=True)
print(stu)
stu.sort(key=lambda x: x.grade)
print(stu)
# 以上操作就等价于下面的操作
stu.sort(key=lambda x: (x.grade, -x.age))
print(stu)
# 输出结果
[('jane', 'B', 12), ('john', 'A', 11), ('dave', 'B', 10), ('maye', 'A', 9)]
[('john', 'A', 11), ('maye', 'A', 9), ('jane', 'B', 12), ('dave', 'B', 10)]
[('john', 'A', 11), ('maye', 'A', 9), ('jane', 'B', 12), ('dave', 'B', 10)]
03. sorted()和sort()
sort
是列表的方法,而sorted()
是一个内置函数,sorted得到的结果会产生一个新的被排序的变量,之前的变量是不变的。sorted()
基本使用方法和sort基本相同,这里主要讲解下cmp_to_key
用来实现复杂的比较
首先, 查看cmp_to_key
的源码,比较容易理解,实际上我们通过cmp_to_key
实现的大小关系通过和0的比较即可,即我们认为两个值a和b,如果满足a<b,即需要返回给cpm_to_key
一个负数即可
def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function"""
class K(object):
__slots__ = ['obj']
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
__hash__ = None
return K
举例,例如用cmp_to_key
实现按照分数降序,按照年龄升序
from functools import cmp_to_key
class Student:
def __init__(self, name, grade, age):
self.name = name
self.grade = grade
self.age = age
def __repr__(self):
return repr((self.name, self.grade, self.age))
def student_cmp(x, y):
if x.grade == y.grade:
return y.age - x.age
return x.grade - y.grade
stu = [
Student('jane', 99, 12),
Student('john', 78, 11),
Student('dave', 78, 10),
Student('maye', 87, 9),
]
res = sorted(stu, key= cmp_to_key(student_cmp))
print(res)
# 输出结果
[('john', 78, 11), ('dave', 78, 10), ('maye', 87, 9), ('jane', 99, 12)]
关于student_cmp
函数可以这样理解,传入的两个参数x
和y
,代表待排序列表中的元素,这里即是Student
类的实例。现在需要分数降序,按照年龄升序排列,那么首先需要比较x.grade == y.grade
,如果不相等,那么降序排列,即返回负数代表小于即可,如果相等,那么age升序排序,即返回age的正数代表大于即可