一、前言

对Python的列表(list)有两个用于排序的方法:

一个是内建方法list.sort(),可以直接改变列表的内容:

>>> list1 = [9,8,7,6,5]
>>> list1.sort()
>>> list1
[5, 6, 7, 8, 9]

另一个是内建函数sorted(),它的特点是不改变原列表的内容,而是根据一个可迭代对象建立一个新的列表:

>>> list2 = [4,3,2,1]
>>> list3 = sorted(list2)
>>> list2
[4, 3, 2, 1]
>>> list3
[1, 2, 3, 4]

二、基础排序

最简单的升序排序非常容易:直接调用sorted()函数就可以了,它返回一个新的列表:

>>> sorted([5, 2, 3, 1, 4])
[1, 2, 3, 4, 5]

也可以使用列表本身的方法list.sort()去排序。它会改变list的内容,然后返回None作为执行的结果,以避免混淆。一般来说它没有sorted()那么方便,但是如果你不需要原来的列表的话,使用它在性能上会有轻微的提升。

>>> a = [5, 2, 3, 1, 4]
>>> a.sort()
>>> a
[1, 2, 3, 4, 5]

另一个区别就是,list.sort()方法只能用于列表,相对的,sorted()函数则适用于所有的可迭代对象,如:

>>> sorted({1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'})
[1, 2, 3, 4, 5]

三、key函数

从Python2.4开始,无论是list.sort()还是sorted()都增加了一个key参数,指定一个在进行比较之前作用在每个列表元素上的函数。

例如,以下就是大小写不敏感的字符串比较:

>>> sorted("This is a test string from Andrew".split(), key=str.lower)
['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']

key参数对应的值,必须是这样一个函数:接受一个参数然后返回一个用来排序的键。用这种技术来排序在速度上是非常快的,因为key函数恰好被每一个输入记录调用一次。

一种常用的模式是,在对复杂对象进行排序的时候,使用这个对象的索引作为排序的键,例如:

>>> student_tuples = [
... ('john', 'A', 15),
... ('jane', 'B', 12),
... ('dave', 'B', 10),
... ]
>>> sorted(student_tuples, key=lambda student: student[2]) # sort by age
[('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)) >>> student_objects = [
... Student('john', 'A', 15),
... Student('jane', 'B', 12),
... Student('dave', 'B', 10),
... ]
>>> sorted(student_objects, key=lambda student: student.age) # sort by age [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

四、使用Operator模块的函数

上面提到的key函数在排序中是很常用的,因此Python本身也提供了很多很方便的函数,让创建访问器函数变得更快、更容易。operater模块提供的函数有operator.itemgetter(),operator.attrgetter(),另外从Python2.5开始新增了operator.methodcaller()函数。

使用这些函数,上面的例子可以变得更简单和更快:

>>> from operator import itemgetter, attrgetter
>>> sorted(student_tuples, key=itemgetter(2))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
>>> sorted(student_objects, key=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

operator模块提供的几个函数允许多级别的排序。例如,根据grade和age进行排序:

>>> sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
>>> sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

operator.methodcaller()函数会在每个被排序的对象上执行一个固定参数的方法调用,例如,str.count()方法可以通过统计在每个消息中感叹号的数量,来计算消息的优先度:

>>> from operator import methodcaller
>>> messages = ['critical!!!', 'hurry!', 'standby', 'immediate!!']
>>> sorted(messages, key=methodcaller('count', '!'))
['standby', 'hurry!', 'immediate!!', 'critical!!!']

五、升序和降序

list.sort()和sorted()都接受一个reverse参数,这个参数的取值是一个布尔值,用来标记是否降序排序。例如,用age字段降序排列去获取学生信息:

>>> sorted(student_tuples, key=itemgetter(2), reverse=True)
[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
>>> sorted(student_objects, key=attrgetter('age'), reverse=True)
[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

六、排序稳定性及复杂排序

从Python2.2版本开始,排序都是稳定的,也就是说如果有两个记录他们的排序键相等,则排序前后他们的原始顺序是固定不变的。

>>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
>>> sorted(data, key=itemgetter(0))
[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

观察可以得知两个排序键同样是’blue’的记录,他们的原始顺序在排序前后没有改变,保证(‘blue’, 1)在(‘blue’, 2)前面。

这种极佳的属性可以让你用一系列排序的步骤去创建一种复杂的排序。例如,用grade字段降序,age字段升序去排序学生数据,age字段先排,grade字段后排。

>>> s = sorted(student_objects, key=attrgetter('age'))     # sort on secondary key
>>> sorted(s, key=attrgetter('grade'), reverse=True) # now sort on primary key, descending
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Python的Timsort算法可以高效率地进行多重排序,因为它能很好的利用数据集中已经存在的有序序列。

七、使用decorate-sort-undecorated老方法

这个习语是以它的三个步骤而被命名为decorate-sort-undecorate(装饰-排序-去装饰)的:

  • 首先,原始的列表被装饰以生成新的值,这些值是用来控制排序顺序的。
  • 然后,对被装饰过的列表进行排序。
  • 最后,去掉装饰,以新的顺序创建一个列表,这个列表只包含原来列表中的值。

例如,使用DSU方法用学生数据中的grade字段对其进行排序:

>>> decorated = [(student.grade, i, student) for i, student in enumerate(student_objects)]
>>> decorated.sort()
>>> [student for grade, i, student in decorated] # undecorate
[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

在这里,元组是按照字典序进行比较的,首先第一项被比较,如果他们相等则对第二项进行比较,以此类推。

不是在所有的情况下都需要在被装饰的列表中包含下标i,但是包含它会有两个好处:

让排序稳定——如果两个项拥有一样的键,那么他们在排序列表中的顺序不会改变。

原始的项不需要是可对比的,因为被装饰的元组的顺序最多被前面两个项决定。举个例子,原始列表可以包含不能直接排序的复数。

这个习语的另一个名字是Schwartzian transform,以Randal L. Schwartz进行命名,因为他用Per语言推广了这种方法。

对于大型的列表,以及那些计算对比关系的代价很昂贵的列表来说,在Python的2.4版本之前,DSU几乎是排序这类列表的最快的方法。但是在2.4及之后的版本,key函数就提供了一样的功能了。

八、使用cmp参数老方法

在这篇文章中提到的很多设计,其实都是Python2.4或之后的版本才给出的。在此之前,无论sorted()函数还是list.sort()方法都没有不带参的调用方式。相反的,所有Py2.x版本支持cmp参数,来处理用户指定的对比函数。

在Python3中,cmp参数是完全被移除了(这是对简化和统一这门语言作出的努力的一部分,另外也消除了富比较(rich comparisons )和__cmp()__魔术方法的冲突)。

在Python2中,sort()允许指定一个可选的函数,这个函数作为比较的用途被调用。这个函数必须带两个被用来比较的参数,然后返回一个值,如果小于是负数,如果相等是0,如果大于则返回正数。例如,我们可以这么做:

>>> def numeric_compare(x, y):
... return x - y
>>> sorted([5, 2, 4, 1, 3], cmp=numeric_compare)
[1, 2, 3, 4, 5]

或者你也可以反转对比的顺序:

>>> def reverse_numeric(x, y):
... return y - x
>>> sorted([5, 2, 4, 1, 3], cmp=reverse_numeric)
[5, 4, 3, 2, 1]

从Python2.x导入代码到3.x的时候,如果你使用这种比较函数的时候,代码会报错,然后你就需要把它转为一个key函数了。以下的包装器可以让这个转化过程变得更简单:

def cmp_to_key(mycmp):
'Convert a cmp= function into a key= function'
class K(object):
def __init__(self, obj, *args):
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
def __ne__(self, other):
return mycmp(self.obj, other.obj) != 0
return K

想转化成一个key函数,直接包装旧的对比函数就可以了:

>>> sorted([5, 2, 4, 1, 3], key=cmp_to_key(reverse_numeric))
[5, 4, 3, 2, 1]

在Python2.7中,functools.cmp_to_key()函数已经被增加到functools模块中了。

九、其他

对于浮点数的排序,使用locale.strxfrm()作为key函数,或者locale.strcoll()作为对比函数。

reverse参数依旧保持排序的稳定性。有趣的是,这个效果可以通过调用内建函数reversed()两次进行模拟:

>>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
>>> standard_way = sorted(data, key=itemgetter(0), reverse=True)
>>> double_reversed = list(reversed(sorted(reversed(data), key=itemgetter(0))))
>>> assert standard_way == double_reversed
>>> standard_way
[('red', 1), ('red', 2), ('blue', 1), ('blue', 2)]

为一个类创建一种标准的排序顺序,只需要增加合适的富比较函数即可:

>>> Student.__eq__ = lambda self, other: self.age == other.age
>>> Student.__ne__ = lambda self, other: self.age != other.age
>>> Student.__lt__ = lambda self, other: self.age < other.age
>>> Student.__le__ = lambda self, other: self.age <= other.age
>>> Student.__gt__ = lambda self, other: self.age > other.age
>>> Student.__ge__ = lambda self, other: self.age >= other.age
>>> sorted(student_objects)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

对于一般用途的比较,一般的方法建议定义全部六个富对比操作符。现在functools.total_ordering() 类装饰器让这一切变得更加容易实现。

key函数不一定要直接基于被排序的对象。key函数同样可以访问外部的资源。举个例子,如果学生的年级被存储在一个字典之中,那它就可以用来为另一个单独的的学生的名字组成的列表进行排序:

>>> students = ['dave', 'john', 'jane']
>>> grades = {'john': 'F', 'jane':'A', 'dave': 'C'}
>>> sorted(students, key=grades.__getitem__)
['jane', 'dave', 'john']

以上内容翻译自Python2.7.15文档,Sorting HOW TO。

(完)

Python:如何排序(sort)的更多相关文章

  1. python之排序(sort/sorted)

    大家都知道,python排序有内置的排序函数 sort() 和 高阶函数sorted() .但是它们有什么区别呢? 让我们先从这个函数的定义说起: sorted():该函数第一个参数iterable为 ...

  2. python 字典排序 关于sort()、reversed()、sorted()

    一.Python的排序 1.reversed() 这个很好理解,reversed英文意思就是:adj. 颠倒的:相反的:(判决等)撤销的 print list(reversed(['dream','a ...

  3. <转>python字典排序 关于sort()、reversed()、sorted()

    一.Python的排序 1.reversed() 这个很好理解,reversed英文意思就是:adj. 颠倒的:相反的:(判决等)撤销的 print list(reversed(['dream','a ...

  4. Python 列表排序方法reverse、sort、sorted操作方法

    python语言中的列表排序方法有三个:reverse反转/倒序排序.sort正序排序.sorted可以获取排序后的列表.在更高级列表排序中,后两中方法还可以加入条件参数进行排序. reverse() ...

  5. python下使用sort()函数对目录下文件名进行多条件排序

    目录 1.基础函数 2.例子解析 参考 1.基础函数 a.sort()函数 sort()函数的作用是对列表内容进行正向排序,直接在原列表进行修改,返回的是修改后的列表. lists =[1, 5, 1 ...

  6. python sorted排序

    python sorted排序 Python不仅提供了list.sort()方法来实现列表的排序,而且提供了内建sorted()函数来实现对复杂列表的排序以及按照字典的key和value进行排序. s ...

  7. Python的排序

    1.reversed() 这个很好理解,reversed英文意思就是:adj. 颠倒的:相反的:(判决等)撤销的 print list(reversed(['dream','a','have','I' ...

  8. Python之排序算法:快速排序与冒泡排序

    Python之排序算法:快速排序与冒泡排序 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/7828610.html 入坑(简称IT)这一行也有些年头了,但自老师 ...

  9. python常见排序算法解析

    python——常见排序算法解析   算法是程序员的灵魂. 下面的博文是我整理的感觉还不错的算法实现 原理的理解是最重要的,我会常回来看看,并坚持每天刷leetcode 本篇主要实现九(八)大排序算法 ...

  10. 第四百一十五节,python常用排序算法学习

    第四百一十五节,python常用排序算法学习 常用排序 名称 复杂度 说明 备注 冒泡排序Bubble Sort O(N*N) 将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮 ...

随机推荐

  1. flex布局在垂直居中里,元素超过容器大小后,不能通过滚动条滚动到顶端,这是个flex的bug

    The Problem Flexbox makes centering very easy. By simply applying align-items: center and justify-co ...

  2. 送人玫瑰,手留余香——2015年技术分享交流小结

    飞测说:分享让我们更加团结,交流让我们更加凝聚,送人玫瑰,手留余香,更多分享交流也让自己成长的更加完善,2015年已经过去了好几个月,今天刚好整理了下我们科大讯飞武汉测试团队技术分享交流的这块,顺便做 ...

  3. 第24课 #pragma使用分析

    #pragma是C语言留给编译器厂商进行扩展用的. 这个关键字在不同的编译器之间也许是不能够移植的. #pragma简介 #pragma message #pragma message打印的消息并不代 ...

  4. 量化分析:把Tushare数据源,规整成PyalgoTrade所需格式

    量化分析:把Tushare数据源,规整成PyalgoTrade所需格式 分析A股历史数据,首先需要确定数据来源.如果只想做日k线.周k线的技术分析,可以用PyalgoTrade直接从yahoo.goo ...

  5. 2018c语言第2次作业

    1 删除字符串中数字字符 1.设计思路 (1)主要描述题目算法 第一步:先用for循环比较每个数是否符合删除条件. 第二步:如果符合就把这个数利用交换把这个数提前一位. 2.实验代码 void del ...

  6. 注册dll文件

    1.打开"开始-运行-输入regsvr32 XXX.dll",回车即可 2.win7 64位旗舰版系统运行regsvr32.exe提示版本不兼容 在运行regsvr32.exe的时 ...

  7. 在AD中存取照片

    AD中有存放照片的字段吗? 答案肯定是有的.photo,jpegPhoto,thumbnailPhoto 前端时间客户,包括领导 在问通讯录中的照片为什么存在数据库中而不是AD中,AD中的属性能不能利 ...

  8. TimeExit 界面无点击定时退出类

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.W ...

  9. 配置 host only 后 nat不能上网了

    如果只有nat 网关为nat 中设置的网关  eth0 启动第二块网卡host_only 网关就变成了 host_only中的网关  eth1 解决放法 route -n 看启用的是哪个网关 [roo ...

  10. Mysql插入数据报错java.sql.SQLException: Incorrect string value: '\xF0\x9F\x93\x8D\xE6\x88...'

    今天读取solr里面的数据,往mysql插入时报错, Incorrect string value: '\xF0\x9F\x93\x8D\xE8\x88...' for column 'title'  ...