0 前言
有時候我們需要自定義排序的規則,但是又不想自己再寫一遍排序函數,所以庫中的排序函數一般都支持我們自定排序規則,然後以函數的形式傳遞給排序函數,就完成了自定義排序的需求。之前一直用C++,知道C++中的排序函數sort是可以實現這種需求的。現在轉到python3突然發現python3提供的sort函數沒有了cmp這個參數(python2是有的),但是幸運的是,我們還可以通過其他的方法實現這種自定義排序規則的需求。
1 python2中的sort和sorted
1.1 sort()函數
sort函數的定義如下:
sort(self, cmp=None, key=None, reverse=False)
- self:表示list自身
- cmp:自定的比較函數
- key:指定元素在比較之前要調用的函數,並且這個函數接受一個參數,返回一個作爲排序依據的key。
- reverse:當爲False時,表示增序排列,爲True時爲降序排列。
sort函數僅僅對列表對象進行排序,會改變list元素自身的順序,沒有返回值也可以認爲返回值爲None。調用的形式爲list.sort()
python2中使用sort函數自定義排序函數來進行排序:
a = [('ZhangSan',24),('Lisi', 22) , ('WangWu', 20)]
#python中升序排序的比較函數的邏輯
def cmp(a, b):
if a < b:
return -1
elif a > b:
return 1
else:
return 0
print a
a.sort(cmp=cmp, key=lambda x: x[1])
print a
輸出結果:
[('ZhangSan', 24), ('Lisi', 22), ('WangWu', 20)]
[('WangWu', 20), ('Lisi', 22), ('ZhangSan', 24)]
1.2 sorted()函數
sorted()函數的定義:
sorted(iterable, cmp=None, key=None, reverse=False)
- iterable表示可迭代的類型
- 其他的參數和sort()函數一致
sorted函數與sort函數相比有兩點主要的不同:
- sorted函數不會改變原來的可迭代的集合,而是會返回一個新的排序好的列表(list)
- sort函數只能用於list,sorted函數可以用於任意可迭代的類型
python2中使用sorted函數自定義排序函數來進行排序:
a = [('ZhangSan',24),('Lisi', 22) , ('WangWu', 20)]
#python中升序排序的比較函數的邏輯
def cmp(a, b):
if a < b:
return -1
elif a > b:
return 1
else:
return 0
print a
b = sorted(a, cmp, key=lambda x: x[1])
print a
print b
2 python3中的sort和sorted
2.1 在python3中使用cmp函數
在python3中sort函數和sorted函數都有所變化,他們的cmp參數都被去掉了。沒有了cmp函數怎麼進行自定義排序呢?其實python提供了另一種方法。就是使用functools.cmp_to_key函數將比較函數轉換爲key的參數。
import functools
a = [('ZhangSan',24),('Lisi', 22) , ('WangWu', 20), ('WangWu', 19)]
#python中升序排序的比較函數的邏輯
def cmp(a, b):
if a < b:
return -1
elif a > b:
return 1
else:
return 0
print (a)
a.sort(key=functools.cmp_to_key(cmp))
print (a)
[('ZhangSan', 24), ('Lisi', 22), ('WangWu', 20), ('WangWu', 19)]
[('Lisi', 22), ('WangWu', 19), ('WangWu', 20), ('ZhangSan', 24)]
可以看到,對於元組,默認按照第一個元素排序,第一個元素相同的話會比較第二個元素,依次往後比較。cmp函數需要兩個參數,functools模板中的functools.cmp_to_key()就可以把原來有兩個參數的cmp轉化成了符合key參數要求的一個參數的函數。
cmp_to_key
函數的源碼如下:
def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function"""
class K(object):
__slots__ = ['obj']
def __init__(self, obj):
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
__hash__ = None
return K
返回的是一個類,在 sort 內部,類接收一個參數構造一個實例,然後實例通過重載的方法來進行比較。
sorted函數同樣支持上面的用法。
2.2 python3中自定義排序
上面介紹的是怎樣在python3中使用cmp函數來自定義排序,cmp函數有兩個參數,是python2的產物,在python中提倡使用key function,key function接收一個參數。我們完全可以自定義的排序規則定義爲key function,而不是定義爲cmp函數再使用cmp_to_key轉換成key function。
必須再次明確key_function的作用:指定元素在比較之前要調用的函數,並且這個函數接受一個參數,返回一個作爲排序依據的key。
下面是幾個例子來幫助,我們適應使用key_function這種方式來自定義排序規則。
例子1:
my_alphabet = ['a', 'b', 'c']
def custom_key(word):
numbers = []
for letter in word:
numbers.append(my_alphabet.index(letter))
return numbers
# python中的整數列表能夠比較大小
# custom_key('cbaba')==[2, 1, 0, 1, 0]
x=['cbaba', 'ababa', 'bbaa']
x.sort(key=custom_key)
print(x)
['ababa', 'bbaa', 'cbaba']
例子2:
students = [('john', 'A', 15), ('jane', 'B', 12), ('dave','B', 10)]
ns = sorted(students,key=lambda x: x[2]) #按照年齡來排序
print(ns)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
例子3:使用對象的屬性作爲key值
>>> 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)]
例子4:使用多個多個關鍵字進行排序
>>> list = [('d',3),('a',5),('d',1),('c',2),('d',2)]
>>> print (sorted(list, key = lambda x:(x[0],x[1])))
[('a', 5), ('c', 2), ('d', 1), ('d', 2), ('d', 3)]
例子5:
默認sorted([True, False])==[False, True] (False=0 < True=1)
一個字符串排序,排序規則:小寫<大寫<奇數<偶數
#元組內(e1, e2, e3)的優先級排列爲: e1 > e2 > e3
s = 'asdf234GDSdsf23'
ns = sorted(s, key=lambda x: (x.isdigit(),x.isdigit() and int(x) % 2 == 0,x.isupper(),x)
print(ns)
#input: 'asdf234GDSdsf23'
#output: ['a', 'd', 'd', 'f', 'f', 's', 's', 'D', 'G', 'S', '3', '3', '2', '2', '4']
- x.isdigit()的作用是把數字放在後邊(True),字母放在前面(False).
- x.isdigit() and int(x) % 2 == 0的作用是保證數字中奇數在前(False),偶數在後(True)。
- x.isupper()的作用是在前面基礎上,保證字母小寫(False)在前大寫在後(True).
- 最後的x表示在前面基礎上,對所有類別數字或字母排序。
小結:可以看到key_function非常靈活,我們可以定義各種各樣的排序規則。其實python2中同樣具有sort和sorted函數也是有key參數的,同樣可以使用key function定義排序規則。
3 operator.itemgetter或者operator.attrgetter
首先介紹一下:operator模塊。operator模塊封裝了python的一系列操作符對應的函數,某些情況下我們需要爲函數傳遞一個操作符對應的函數,而不能直接傳遞一個操作符
這裏僅僅介紹operator下面三種操作符在自定義排序中的應用。
- operator.itemgetter() ----- 通過下標獲取屬性
- operator.attrgetter() ----- 通過參數獲取屬性
- operator.methodcaller() -----通過函數名獲取函數,python 2.5 被引入,下文詳細介紹
from operator import itemgetter, attrgetter
student_tuples = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10),]
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),]
print(sorted(student_tuples, key=itemgetter(2)))
print(sorted(student_objects, key=attrgetter('age')))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
這個操作模塊也允許多層次的進行排序,例如可以先排序 “成績grand” 再排序 “年齡age”
print(sorted(student_tuples, key=itemgetter(1,2)))
print(sorted(student_objects, key=attrgetter('grade', 'age')))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
itemgetter和attrgetter比較好理解,methodcaller其實不太好理解,關於methodcaller的詳細介紹參考博客:python中的operator模塊
下面看一個例子:
from operator import methodcaller
messages = ['critical!!!', 'hurry!', 'standby', 'immediate!!']
print(sorted(messages, key=methodcaller('count', '!')))
['standby', 'hurry!', 'immediate!!', 'critical!!!']
上面的例子完成的功能是,根據字符串中!的數量對字符串排序,!數量少的排在前面,多的排在後面。
爲了幫助理解以上代碼,補充一個知識,python的count()
函數:
描述: count() 方法用於統計字符串裏某個字符出現的次數。可選參數爲在字符串搜索的開始與結束位置。
語法: str.count(sub, start= 0,end=len(string))
參數:
- sub – 搜索的子字符串
- start – 字符串開始搜索的位置。默認爲第一個字符,第一個字符索引值爲0。
- end – 字符串中結束搜索的位置。字符中第一個字符的索引爲 0。默認爲字符串的最後一個位置。
4 排序的穩定性
從python2.2版本開始,python的排序是保障穩定性的,意思就是說,當複雜的排序中,對象有相同的key的時候,會保持原有的相對順序不變:
data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
print(sorted(data, key=itemgetter(0)))
[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]
5 一個應用實例
具有串文件名['file1.txt', 'file2.txt', 'file10.txt']
需要按照文件名的數字後綴對文件名進行排序:
import re
re_digits = re.compile(r'(\d+)')
def emb_numbers(s):
pieces = re_digits.split(s)
pieces[1] = map(int, pieces[1])
return pieces
def sort_strings_with_emb_numbers(alist):
aux = [(emb_numbers(s), s) for s in alist]
aux.sort()
return [s for __, s in aux]
def sort_strings_with_emb_numbers2(alist):
return sorted(alist, key=emb_numbers)
filelist = 'file10.txt file2.txt file1.txt'.split()
print (filelist)
print ('--DSU排序')
print (sort_strings_with_emb_numbers(filelist))
print ('--內置DSU排序')
print (sort_strings_with_emb_numbers2(filelist) )
['file10.txt', 'file2.txt', 'file1.txt']
--DSU排序
['file1.txt', 'file10.txt', 'file2.txt']
--內置DSU排序
['file1.txt', 'file10.txt', 'file2.txt']
參考文章: