【Effective Python】用sort/sorted方法的key参数来表示复杂的排序逻辑

用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函数可以这样理解,传入的两个参数xy,代表待排序列表中的元素,这里即是Student类的实例。现在需要分数降序,按照年龄升序排列,那么首先需要比较x.grade == y.grade,如果不相等,那么降序排列,即返回负数代表小于即可,如果相等,那么age升序排序,即返回age的正数代表大于即可

上一篇:MySQL常用命令记录


下一篇:mysql分组排序