这篇博客介绍了Python(3.3)中的序列数据类型和对象的排序,在原文基础上有所修改。
原文链接:http://docs.python.org/3/howto/sorting.html
基本排序:
Python中的列表(list)有两个内建排序函数
1.list.sort(),这会替换原列表:
sort(*, key=None, reverse=None)
2.sorted()函数会接受一个可迭代序列,返回一个新列表,注意是列表(序列进去,列表出来):
sorted(iterable[, key][, reverse])
下面是一个升序排序的简单例子:
>>> sorted([5, 2, 3, 1, 4])
[1, 2, 3, 4, 5]
>>> 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]
接受参数的排序:
同C++和其他高级语言一样,Python同样接受排序参数来使排序函数按照特定的比较规则进行排序。
>>> sorted("This is a test string from Andrew".split(), key=str.lower)
[’a’, ’Andrew’, ’from’, ’is’, ’string’, ’test’, ’This’]
>>> sorted("This is a test string from Andrew".split())
[’Andrew’,’This’,’a’, ’from’, ’is’, ’string’, ’test’, ]
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:
Operator模块中的itemgetter(), attrgetter(), 和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)]
我们也可以先按成绩,后按年龄来排序:
>>> 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)]
升序与降序:
除了key参数以外,sorted与sort函数同样接受reverse参数,reverse参数是一个布尔值,True和False分别对应升序与降序:
>>> 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)]
顺序稳定性与复杂排序:
在一次排序中,排序函数只会改变需要改变的对象位置,例如我们按照年龄排序,当存在两个相同年龄的人时,排序函数不会改变这两个人在原序列中的顺序。
>>> data = [(’red’, 1), (’blue’, 1), (’red’, 2), (’blue’, 2)]
>>> sorted(data, key=itemgetter(0))
[(’blue’, 1), (’blue’, 2), (’red’, 1), (’red’, 2)]
这一属性允许我们在序列中使用一系列的步骤进行排序,而不会出现逻辑问题。例如我们想在student对象中按成绩降序排列,且按年龄升序排列。
>>> s = sorted(student_objects, key=attrgetter(’age’))
# sort on secondary key
>>> sorted(s, key=attrgetter(’grade’), reverse=True)
# then sort on primary key, descending
[(’dave’, ’B’, 10), (’jane’, ’B’, 12), (’john’, ’A’, 15)]
在Python中使用的是Timsort排序算法,这使python的复杂排序效率非常之高。
2.X版本中的DSU排序:
DSU( Decorate-Sort-Undecorate)排序包含三个步骤:
1.为要排序的列表添加一个新值--决定列表顺序的值,这个新的列表称为Decorated-List(Decorate)。
2.排序(sort)。
3.还原拥有了新顺序的原列表,即删除掉1中创建的顺序控制值(Undecorate)。
>>> 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并不是必需的,但包含i有这么两点好处:
1.顺序稳定性。元组按照字典序(先第一个比,若相同第二个比)比较,包含进i可使在grade相同的情况下,保持住原列表中的顺序。
2.防止元列表对象无法进行比较。包含i使得decorated-list最多只可比较两个元素,第三个元素(例中的student)比较不到。当第三个元素是不可比较的(如复数),这可拯救一次错误的排序,使得列表得以有稳定顺序。
DSU在perl编程中称作 Schwartzian transform,在python中,当我们拥有了key参数后,这个技巧已经不是必需的了。
2.X版本中的cmp参数:
在3.0以后的版本中,cmp参数被彻底移除。
在Python2.X版本中,我们可以写出一个cmp函数,并将其传入sort函数,像这样:
>>> def numeric_compare(x, y):
return x - y
>>> sorted([5, 2, 4, 1, 3], cmp=numeric_compare)
[1, 2, 3, 4, 5]
#Or you can reverse the order of comparison with:
>>> def reverse_numeric(x, y):
return y - x
>>> sorted([5, 2, 4, 1, 3], cmp=reverse_numeric)
[5, 4, 3, 2, 1]
在3.X中,虽然不再提供cmp,但我们可以使用标准库functools的 functools.cmp_to_key()函数来转换cmp函数:
>>> sorted([5, 2, 4, 1, 3], key=functools.cmp_to_key(reverse_numeric))
[5, 4, 3, 2, 1]
Cmp_to_key的函数实现类似于这样:
def cmp_to_key(mycmp):
’Convert a cmp= function into a key= function’
class K:
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
其他与结束:
1.对象间的比较:在Python中,对象间的比较默认使用__it__()方法。所以我们可以为对象创建__it__()方法来为其定义顺序。
>>> Student.__lt__ = lambda self, other: self.age < other.age
>>> sorted(student_objects)
[(’dave’, ’B’, 10), (’jane’, ’B’, 12), (’john’, ’A’, 15)]
2.key的函数不仅仅局限于要排序的序列内,它可以扩展到一个外部序列,当然这个外部序列需要匹配需要排序的键值。
>>> students = [’dave’, ’john’, ’jane’]
>>> newgrades = {’john’: ’F’, ’jane’:’A’, ’dave’: ’C’}
>>> sorted(students, key=newgrades.__getitem__)
[’jane’, ’dave’, ’john’]
在The Art of Computer Programming这本书中,作者指出计算机有25%的时间在进行排序操作。我们不知道这个数字是否准确,但排序的确是计算机科学中极为重要的一部分。