翻译:如何排序
Python的list有一个内建函数sort()方法,它是更改元素在list中的位置来排序,另一个内建函数sorted()是从可迭代的对象新建一个排序后的list返回。
进行排序的方法有很多种,下面我们来看一看。
向后兼容摘记
许多介绍资料中,都没有提到python2.4中如何排序。在2.4版本之前,没有内建方法sorted(),并且,list.sort()没有关键字参数。
基本数据类型排序
简单的升序排序是非常容易的,只需要调用sorted()函数即可,它将返回一个新的排过序的list。
- >>> print sorted([5, 2, 3, 1, 4])
- [1, 2, 3, 4, 5]
也可以用list的sort()方法,它在原list上修改(返回None,避免引起迷惑),通常,如果不需要原始的list,sorted()使用得会更多一下,但是它小巧并且效率更高。
- >>> ls1 = [5, 2, 3, 1, 4]
- >>> ls1.sort()
- >>> print ls1
- [1, 2, 3, 4, 5]
排序的比较函数必须能够经比较后得出结果,默认使用的比较函数等同于使用内建函数cmp:
- >>> print sorted([5, 2, 3, 1, 4], cmp)
- [1, 2, 3, 4, 5]
内建cmp函数是比较两个对象,x<y返回负数,x=y返回0,x>y返回正数。在排序时,比较函数必须对比较对象保持一致的合理比较操作。
当然了,也可以使用自定义的比较函数。比如正数比较:
- >>> def numeric_compare(x,y):
- ... return x - y
- ...
- >>>
或者,也可以这样定义(有些繁冗):
- >>> def numeric_compare(x, y):
- ... if x > y:
- ... return 1
- ... elif x == y:
- ... return 0
- ... else: # x < y
- ... return -1
- ...
- >>> ls1 = [5, 2, 3, 1, 4]
- >>> ls1.sort(numeric_compare)
- >>> print ls1
- [1, 2, 3, 4, 5]
顺便说一下,如果减法操作的结果超出最大整数范围,如:sys.maxint - (-1),sort()函数就不会起作用了。
或者,也可以使用lambda定义一个匿名函数,如:
- >>> ls1 = [5, 2, 3, 1, 4]
- >>> ls1.sort(lambda x, y : x - y)
- >>> print ls1
- [1, 2, 3, 4, 5]
Python2.4中,sort()方法中新增了三个关键字参数:cmp, key, reverse。cmp是排序函数,前面的例子可以改写为:
- >>> ls1.sort(cmp=numeric_compare)
- >>> ls1.sort(cmp=lambda x, y: x - y)
reverse参数是布尔类型,为True时,翻转升序排序的结果。
- >>> ls1 = [5, 2, 3, 1, 4]
- >>> ls1.sort(reverse=True)
- >>> ls1
- [5, 4, 3, 2, 1]
在Python2.4之前,翻转比较结果
- >>> ls1 = [5, 2, 3, 1, 4]
- >>> def numeric_compare(x,y):
- ... return y - x
- ...
- >>>
- >>> ls1.sort(numeric_compare)
- >>> print ls1
- [5, 4, 3, 2, 1]
更普遍的写法是 return cmp(y,x)或者-cmp(x,y)。
然而,更高效的使用方法是先排序,再调用reverse()方法
- >>> ls1 = [5, 2, 3, 1, 4]
- >>> ls1.sort()
- >>> ls1.reverse()
- >>> ls1
- [5, 4, 3, 2, 1]
- >>>
根据keys排序
Python2.4的参数key可以让你得到迭代list的每个元素的key,然后使用这个key,如:
- >>> a = "This is a test string from Andrew".split()
- >>> a.sort(key=str.lower)
- >>> a
- ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
key的值必须是接受一个参数的函数,该函数返回的值作为排序用
内建函数基本上是我们常常使用的,比如string.lower(),operator模块包含了大量的有用函数,比如,可以使用operator.itemgetter():
- >>> import operator
- >>> ls = [ ('c', 2), ('d', 1), ('a', 4), ('b', 3) ]
- >>> map(operator.itemgetter(0), ls)
- ['c', 'd', 'a', 'b']
- >>> map(operator.itemgetter(1), ls)
- [2, 1, 4, 3]
- >>> sorted(ls, key=operator.itemgetter(1))
- [('d', 1), ('c', 2), ('b', 3), ('a', 4)]
在Python2.4之前的版本中,没有提供sort()的key参数,所以需要自己写
- >>> a = "This is a test string from Andrew".split()
- >>> a.sort(lambda x, y:cmp(x.lower(), y.lower()))
- >>> print a
- ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
- >>>
比较一下字符串转为小写的效率,耗时O(n log n),但是Python2.4中的耗时是 O(n),它更高效。你自己也可以优化key函数达到高效。
- >>> words = "This is a test string from Andrew".split()
- >>> deco = [(word.lower(), i, word) for i, word in enumerate(words)]
- >>> deco
- [('this', 0, 'This'), ('is', 1, 'is'), ('a', 2, 'a'), ('test', 3, 'test'), ('string', 4, 'string'), ('from', 5, 'from'), ('andrew', 6, 'Andrew')]
- >>> deco.sort()
- >>> deco
- [('a', 2, 'a'), ('andrew', 6, 'Andrew'), ('from', 5, 'from'), ('is', 1, 'is'), ('string', 4, 'string'), ('test', 3, 'test'), ('this', 0, 'This')]
- >>> new_words = [word for _, _, word in deco]
- >>> new_words
- ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
这种风格被称为装饰-排序-反装饰,它包含三步操作:
1.初始list,用控制排序的新值装饰list
2.对装饰后的list排序
3.去除装饰,创建新list,改list包含在新排序后的初始值
类的比较
比较两个基本数据类型,比如int或者string,python内建的函数就可以完成,比较类对象实例,就需要用__cmp__自定义比较函数,如:
- >>> class Spam:
- ... def __init__(self, spam, eggs):
- ... self.spam = spam
- ... self.eggs = eggs
- ... def __cmp__(self, other):
- ... return cmp(self.spam + self.eggs, other.spam + other.eggs)
- ... def __str__(self):
- ... return str(self.spam + self.eggs)
- ...
- >>> a = [Spam(1,4),Spam(9,3),Spam(4,6)]
- >>> a.sort()
- >>> a
- [<__main__.Spam instance at 0x02A21A08>, <__main__.Spam instance at 0x02A219B8>, <__main__.Spam instance at 0x02A21A30>]
- >>> for spam in a:
- ... print str(spam)
- ...
- 5
- 10
- 12
有时候,需要根据class的某个属性排序,最好的办法就是定义__cmp__方法,比较他们的值,但是不能在不同的时间比较不同的值。
Python2.4中有operator.attrgetter()函数,使用它来完成比较更容易一些:
- >>> import operator
- >>> a = [Spam(1,4),Spam(9,3),Spam(4,6)]
- >>> a.sort(key=operator.attrgetter('eggs'))
- >>> for spam in a:
- ... print spam.eggs, str(spam)
- ...
- 3 12
- 4 5
- 6 10
在Python2.4中,如果你不想包含operator模块,那么可以这样做:
- >>> a = [Spam(1,4),Spam(9,3),Spam(4,6)]
- >>> a.sort(key=lambda obj:obj.eggs)
- >>> for spam in a:
- ... print spam.eggs, str(spam)
- ...
- 3 12
- 4 5
- 6 10
再提一次,Python早期版本中,你需要这样做:
- >>> a = [Spam(1,4),Spam(9,3),Spam(4,6)]
- >>> a.sort(lambda x, y :cmp(x.eggs, y.eggs))
- >>> for spam in a:
- ... print spam.eggs, str(spam)
- ...
- 3 12
- 4 5
- 6 10
如果你想比较随意的两个属性,你可以定义自己的比较函数对象,这将使用实例的__call__方法,如:
- >>> class CmpAttr:
- ... def __init__(self, attr):
- ... self.attr = attr
- ... def __call__(self, x, y):
- ... return cmp(getattr(x, self.attr), getattr(y, self.attr))
- ...
- >>> a = [Spam(1,4),Spam(9,3),Spam(4,6)]
- >>> a.sort(CmpAttr('spam'))
- >>> for spam in a:
- ... print spam.spam, spam.eggs, str(spam)
- ...
- 1 4 5
- 4 6 10
- 9 3 12
当然,如果你想取得更快的排序速度,你可以把对象的属性作为组成一个list类型的中间值,然后对中间值进行排序。
我们再来总结一下,一共有6中方法对list进行排序:
1.使用默认的方法进行排序
2.使用比较函数进行排序
3.使用reverse,不使用比较函数
4.使用中间值进行排序
5.使用定义了cmp方法的class排序
6.使用排序函数对象