Skip to content

Latest commit

 

History

History
217 lines (166 loc) · 6.12 KB

basic12.md

File metadata and controls

217 lines (166 loc) · 6.12 KB

排序

本文为大家简单总结一下如何在Python中针对不同情况完成排序操作。

排序列表

Python列表自带排序函数,可以对列表自身进行排序:

a = [3, 2, 1, 4, 0]
a.sort()
print(a)
[0, 1, 2, 3, 4]

b = ['d', 'cc', 'ee', 'aa']
b.sort()
print(b)
['aa', 'cc', 'd', 'ee']

sort是列表的方法,所以需要以a.sort的方式运行,它会将a就地修改,排序后a就不存在了。此外,sort默认为升序,可以通过reverse=True参数改为降序:

a.sort(reverse=True)
print(a)
[4, 3, 2, 1, 0]

一个列表中的元素能够排序的关键在于我们能够以一定的方式比较两个元素的大小,这种方式可以是一种原生的规则(例如数字大小,字母表顺序前后等),也可以是自定义的一种规则。上述我们可以将字符串对象排序,是因为:

print('aa' < 'cc')
True

我们知道,比较运算符是由对象的__eq__等一组特殊方法定义的,具体而言,"小于"是由特殊方法__lt__决定的:

class A:
    def __lt__(self, other):
        return False
a1 = A()
a2 = A()
print(a1 < a2)
False
print(a2 < a1)
False

sort正是利用了列表中元素的这些特殊方法获得了它们之间的大小关系,从而进行排序。

考虑这样一个问题,假设我们有一个学生对象的列表,每个学生有三门成绩,对象的__lt__按照总成绩大小比较:

class Student:
    def __init__(self, name, *score):
        self.name = name
        self.math, self.english, self.python = score
        
    @property
    def total(self):
        return self.math + self.english + self.python
    
    def __lt__(self, other):
        return self.total < other.total
      
    def __repr__(self):
        return self.name
      
Mary = Student('Mary', 100, 90, 80)
Tom = Student('Tom', 80, 100, 100)
Jon = Student('Jon', 90, 92, 95)

stu = [Mary, Tom, Jon]
stu.sort()
print(stu)
# [Mary, Jon, Tom]

这是,如果我想分别按照各科成绩排序该怎么办呢?显然,修改__lt__是不合适的。幸运的是,sort允许我们通过一个参数key来指定需要比较的目标。key接收一个函数,函数只有一个参数。sort会将每个元素先输入函数中,再将得到的结果进行比较:

# 按数学排序
stu.sort(key=lambda x: x.math) # key为单参数函数
print(stu)
# [Tom, Jon, Mary]

# 按Python排序
stu.sort(key=lambda x: x.python) # 
print(stu)
# [Mary, Jon, Tom]

另一种比较常见的情况是,列表存储的是一个元组,我们希望以第二或第三个元素的值来排序,此时key便派上了用场:

a = [
    ('d', 3, 4),
    ('b', 2, 1),
    ('c', 1, 5),
    ('a', 5),
]
# 直接sort,按照元素顺序进行比较
a.sort()
print(a)
# [('a', 5), ('b', 2, 1), ('c', 1, 5), ('d', 3, 4)]

# 按照第二列排序
a.sort(key=lambda x: x[1])
print(a)
# [('c', 1, 5), ('b', 2, 1), ('d', 3, 4), ('a', 5)]

这里,如果对lambda表达式不够感冒的话,可以采用标准库operator中的一些函数来替换。operator对Python中各类运算均提供了函数式的表达。例如,访问元组的第二个元素:

t = ('d', 3, 4)
print(t[1])
3

from operator import itemgetter
print(itemgetter(1)(t))
3

itemgetter(1)获得了一个能够获取可迭代对象第二个元素的函数,将它作用在t上,就得到了3。同样的,我们也可以利用attrgetter从对象中获得属性。这样,我们可以利用这些函数来完成排序功能:

from operator import attrgetter, itemgetter
stu.sort(key=attrgetter('math'))
print(stu)
# [Tom, Jon, Mary]

a.sort(key=itemgetter(1))
print(a)
# [('c', 1, 5), ('b', 2, 1), ('d', 3, 4), ('a', 5)]

需要说明的是,operator标准库经过了优化,因而它的速度要快于lambda表达式。

Python独特之处在于,**它并不是直接接受类似于cmp的比较函数,而是通过参数key来指明,究竟用对象的什么属性来进行比较,具体的比较方式则由该属性对象自己定义。**这也体现了Python面向对象的特性与一致性。

sorted

如果希望获得一个排序后的副本,可以采用sorted内建函数,它同sort作用类似,只不过可以接受任意可迭代对象,并返回一个排序后的列表:

from operator import itemgetter
a = [
    ('d', 3, 4),
    ('b', 2, 1),
    ('c', 1, 5),
    ('a', 5),
]

sa = sorted(a, key=itemgetter(1))
print(a)
# [('d', 3, 4), ('b', 2, 1), ('c', 1, 5), ('a', 5)]

print(sa)
# [('c', 1, 5), ('b', 2, 1), ('d', 3, 4), ('a', 5)]

由于sorted接受可迭代对象,因而我们可以利用它排序一个字典:

dic = {
    'd': 3,
    'c': 1,
    'a': 5,
    'b': 2,
}
print(dic)
{'d': 3, 'c': 1, 'a': 5, 'b': 2}

sk = sorted(dic)
print(sk)
# ['a', 'b', 'c', 'd']

可以看到,默认按照key进行了排序,并将key返回,所以我们可以重构出排序后的字典:

sdic = {key: dic[key] for key in sorted(dic)}
print(sdic)
{'a': 5, 'b': 2, 'c': 1, 'd': 3}

实际上我们还可以借助operator来实现:

from operator import itemgetter
print({key: value for key, value in sorted(dic.items(), key=itemgetter(0))})
{'a': 5, 'b': 2, 'c': 1, 'd': 3}

如果要按照value排序呢?把0换成1即可:

sdic = {key: value for key, value in sorted(dic.items(), key=itemgetter(1))}
print(sdic)
{'c': 1, 'b': 2, 'd': 3, 'a': 5}

最后,我们看一个小栗子。假设有两个列表,相同位置间的元素是相互对应的,现在希望将列表1排序,同时,列表2元素的对应顺序按照列表1排序后的结果自动调整:

a = [3, 2, 1, 4, 7]
b = ['d', 'c', 'b', 'a', 'e']

sb = [b[ind] for ind, _ in sorted(enumerate(a), key=itemgetter(1))]
print(sorted(a))
print(sb)
[1, 2, 3, 4, 7]
['b', 'c', 'd', 'a', 'e']