python sort、sorted高级排序技巧


Posted in Python onNovember 21, 2014

Python list内置sort()方法用来排序,也可以用python内置的全局sorted()方法来对可迭代的序列排序生成新的序列。

1)排序基础

简单的升序排序是非常容易的。只需要调用sorted()方法。它返回一个新的list,新的list的元素基于小于运算符(__lt__)来排序。

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

[1, 2, 3, 4, 5]

 
你也可以使用list.sort()方法来排序,此时list本身将被修改。通常此方法不如sorted()方便,但是如果你不需要保留原来的list,此方法将更有效。
>>> a = [5, 2, 3, 1, 4]

>>> a.sort()

>>> a

[1, 2, 3, 4, 5]

另一个不同就是list.sort()方法仅被定义在list中,相反地sorted()方法对所有的可迭代序列都有效。
>>> 

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

[1, 2, 3, 4, 5]

2)key参数/函数

从python2.4开始,list.sort()和sorted()函数增加了key参数来指定一个函数,此函数将在每个元素比较前被调用。 例如通过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)]

3)Operator 模块函数

上面的key参数的使用非常广泛,因此python提供了一些方便的函数来使得访问方法更加容易和快速。operator模块有itemgetter,attrgetter,从2.6开始还增加了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)]

4)升序和降序

list.sort()和sorted()都接受一个参数reverse(True or False)来表示升序或降序排序。例如对上面的student降序排序如下:

>>> 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)]

5)排序的稳定性和复杂排序

从python2.2开始,排序被保证为稳定的。意思是说多个元素如果有相同的key,则排序前后他们的先后顺序不变。

>>> 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的前面。
 
更复杂地你可以构建多个步骤来进行更复杂的排序,例如对student数据先以grade降序排列,然后再以age升序排列。
>>> 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)]

6)最老土的排序方法-DSU

我们称其为DSU(Decorate-Sort-Undecorate),原因为排序的过程需要下列三步:
第一:对原始的list进行装饰,使得新list的值可以用来控制排序;
第二:对装饰后的list排序;
第三:将装饰删除,将排序后的装饰list重新构建为原来类型的list;
 

例如,使用DSU方法来对student数据根据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)]
上面的比较能够工作,原因是tuples是可以用来比较,tuples间的比较首先比较tuples的第一个元素,如果第一个相同再比较第二个元素,以此类推。
 

并不是所有的情况下都需要在以上的tuples中包含索引,但是包含索引可以有以下好处:
第一:排序是稳定的,如果两个元素有相同的key,则他们的原始先后顺序保持不变;
第二:原始的元素不必用来做比较,因为tuples的第一和第二元素用来比较已经是足够了。
 

此方法被RandalL.在perl中广泛推广后,他的另一个名字为也被称为Schwartzian transform。
 

对大的list或list的元素计算起来太过复杂的情况下,在python2.4前,DSU很可能是最快的排序方法。但是在2.4之后,上面解释的key函数提供了类似的功能。
 

7)其他语言普遍使用的排序方法-cmp函数

在python2.4前,sorted()和list.sort()函数没有提供key参数,但是提供了cmp参数来让用户指定比较函数。此方法在其他语言中也普遍存在。

在python3.0中,cmp参数被彻底的移除了,从而简化和统一语言,减少了高级比较和__cmp__方法的冲突。

在python2.x中cmp参数指定的函数用来进行元素间的比较。此函数需要2个参数,然后返回负数表示小于,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]

当我们将现有的2.x的代码移植到3.x时,需要将cmp函数转化为key函数,以下的wrapper很有帮助:
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

当需要将cmp转化为key时,只需要:

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

[5, 4, 3, 2, 1]

从python2.7,cmp_to_key()函数被增加到了functools模块中。

8)其他注意事项

* 对需要进行区域相关的排序时,可以使用locale.strxfrm()作为key函数,或者使用local.strcoll()作为cmp函数。

* reverse参数任然保持了排序的稳定性,有趣的时,同样的效果可以使用reversed()函数两次来实现:

>>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]

>>> assert sorted(data, reverse=True) == list(reversed(sorted(reversed(data))))

* 其实排序在内部是调用元素的__cmp__来进行的,所以我们可以为元素类型增加__cmp__方法使得元素可比较,例如:

>>> Student.__lt__ = lambda self, other: self.age < other.age

>>> sorted(student_objects)

[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

* key函数不仅可以访问需要排序元素的内部数据,还可以访问外部的资源,例如,如果学生的成绩是存储在dictionary中的,则可以使用此dictionary来对学生名字的list排序,如下:
>>> students = ['dave', 'john', 'jane']

>>> newgrades = {'john': 'F', 'jane':'A', 'dave': 'C'}

>>> sorted(students, key=newgrades.__getitem__)

['jane', 'dave', 'john']

*当你需要在处理数据的同时进行排序的话,sort(),sorted()或bisect.insort()不是最好的方法。在这种情况下,可以使用heap,red-black tree或treap。

Python 相关文章推荐
python中常用检测字符串相关函数汇总
Apr 15 Python
Python 专题五 列表基础知识(二维list排序、获取下标和处理txt文本实例)
Mar 20 Python
Python实现的读写json文件功能示例
Jun 05 Python
windows下 兼容Python2和Python3的解决方法
Dec 05 Python
python 使用turtule绘制递归图形(螺旋、二叉树、谢尔宾斯基三角形)
May 30 Python
基于torch.where和布尔索引的速度比较
Jan 02 Python
python处理RSTP视频流过程解析
Jan 11 Python
详解pandas中iloc, loc和ix的区别和联系
Mar 09 Python
Python响应对象text属性乱码解决方案
Mar 31 Python
Python dict的常用方法示例代码
Jun 23 Python
python实现简单猜单词游戏
Dec 24 Python
如何使用Python对NetCDF数据做空间相关分析
Apr 21 Python
python中global与nonlocal比较
Nov 21 #Python
python装饰器decorator介绍
Nov 21 #Python
python多线程操作实例
Nov 21 #Python
Python中的闭包详细介绍和实例
Nov 21 #Python
Python多线程同步Lock、RLock、Semaphore、Event实例
Nov 21 #Python
python多进程操作实例
Nov 21 #Python
Python多进程通信Queue、Pipe、Value、Array实例
Nov 21 #Python
You might like
php select,radio和checkbox默认选择的实现方法
2010/05/15 PHP
PHP实现的简单网络硬盘
2015/07/29 PHP
PHP magento后台无法登录问题解决方法
2016/11/24 PHP
PHP输出XML格式数据的方法总结
2017/02/08 PHP
laravel 中某一字段自增、自减的例子
2019/10/11 PHP
php设计模式之正面模式实例分析【星际争霸游戏案例】
2020/03/24 PHP
PHP tp5中使用原生sql查询代码实例
2020/10/28 PHP
为数据添加append,remove功能
2006/10/03 Javascript
JavaScript 轻松搞定快捷留言功能 只需一行代码
2010/04/01 Javascript
漂亮的jquery提示效果(仿腾讯弹出层)
2013/02/05 Javascript
jquery动态加载select下拉框示例代码
2013/12/10 Javascript
js中split和replace的用法实例
2015/02/28 Javascript
JavaScript中的toUTCString()方法使用详解
2015/06/12 Javascript
avalon js实现仿google plus图片多张拖动排序附源码下载
2015/09/24 Javascript
BootstrapTable与KnockoutJS相结合实现增删改查功能【一】
2016/05/10 Javascript
jquery实现全选、不选、反选的两种方法
2016/09/06 Javascript
谈谈JS中常遇到的浏览器兼容问题和解决方法
2016/12/17 Javascript
canvas实现简易的圆环进度条效果
2017/02/28 Javascript
详解Vue用自定义指令完成一个下拉菜单(select组件)
2017/10/31 Javascript
Angular4学习教程之HTML属性绑定的方法
2018/01/04 Javascript
node.js中fs文件系统目录操作与文件信息操作
2018/02/24 Javascript
微信小程序按钮点击跳转页面详解
2019/05/06 Javascript
[02:36]DOTA2英雄基础教程 帕格纳
2014/01/20 DOTA
python装饰器实例大详解
2017/10/25 Python
Python2.7 实现引入自己写的类方法
2018/04/29 Python
Tensorflow 模型转换 .pb convert to .lite实例
2020/02/12 Python
python中可以声明变量类型吗
2020/06/18 Python
django序列化时使用外键的真实值操作
2020/07/15 Python
Python使用内置函数setattr设置对象的属性值
2020/10/16 Python
中国电子产品外贸网站:MiniIntheBox
2017/02/06 全球购物
韩国乐天网上商城:Lotte iMall
2021/02/03 全球购物
测试时代收集的软件测试面试题
2013/09/25 面试题
大学生国庆节65周年演讲稿范文
2014/09/25 职场文书
山东省召开党的群众路线教育实践活动总结大会新闻稿
2014/10/21 职场文书
2015年科普工作总结
2015/07/23 职场文书
如何优化vue打包文件过大
2022/04/13 Vue.js