本文始發於我的公衆號:TechFlow,原創不易,求個關注python
今天的這篇文章和你們聊聊Python當中的排序,和不少高級語言同樣,Python封裝了成熟的排序函數。咱們只須要調用內部的sort函數,就能夠完成排序。可是實際場景當中,排序的應用每每比較複雜,好比對象類型,當中有多個字段,咱們但願按照指定字段排序,或者是但願按照多關鍵字排序,這個時候就不能簡單的函數調用來解決了。編程
咱們先來看下最多見的字典排序的場景,假設咱們有一個字典的數組,字典內有多個字段。咱們但願可以根據字典當中的某一個字段來進行排序,咱們用實際數據來舉個例子:數組
kids = [
{'name': 'xiaoming', 'score': 99, 'age': 12},
{'name': 'xiaohong', 'score': 75, 'age': 13},
{'name': 'xiaowang', 'score': 88, 'age': 15}
]
複製代碼
這裏的kids是一個dict類型的數組,dict當中擁有name, score和age三個字段。假設咱們當下但願可以按照score來排序,應該怎麼辦呢?函數
對於這個問題,解決的方案有不少,首先,咱們可使用上一篇文章當中提到的匿名函數來指定排序的。這裏的用法和上篇文章優先隊列的用法是同樣的,咱們直接來看代碼:工具
sorted(kids, key=lambda x: x['score'])
複製代碼
在匿名函數當中咱們接收的x是kids當中的元素,也就是一個dict,因此咱們想要指定咱們但願的字段,須要用dict訪問元素的方法,也就是用中括號來查找對應字段的值。spa
假如咱們但願按照多關鍵字排序呢?code
首先介紹一下多關鍵字排序,仍是用上面的數據打比方。在上面的例子當中,各個kid的score都不同,因此排序的結果是肯定的。但若是存在兩我的的score相等,我但願年齡小的排在前面,那麼應該怎麼辦呢?咱們分析一下能夠發現,本來是按照分數從小到大排序,但有可能會出現分數相等的狀況。這個時候,咱們但願可以按照在分數相等的狀況下來比較年齡,也就是說咱們但願根據兩個關鍵字來排序,第一個關鍵字是分數,第二個關鍵字是年齡。orm
因爲Python當中支持tuple和list類型的排序,也就是說咱們能夠直接比較[1, 3]和[1, 2]的大小關係,Python會自動一次比較兩個數組當中的元素的大小。若是相等就自動日後比較,直到出現不等或者結束爲止。cdn
明白了這點,其實就很好辦了。咱們只要在匿名函數當中稍稍修改,讓它返回的結果增長一個字段便可。對象
sorted(kids, key=lambda x: (x['score'], x['age']))
複製代碼
除了匿名函數,Python也有自帶的庫能夠解決這個問題。用法和匿名函數很是接近,使用起來稍稍容易一些。
它就是operator庫當中的itemgetter函數,咱們直接來看代碼:
from operator import itemgetter
sorted(kids, key=itemgetter('score'))
複製代碼
若是是多關鍵字也能夠,傳入多個key便可:
sorted(kids, key=itemgetter('score', 'age'))
複製代碼
咱們接下來看一下對象的自定義排序,咱們首先把上面的dict寫成對象:
class Kid:
def __init__(self, name, score, age):
self.name = name
self.score = score
self.age = age
def __repr__(self):
return 'Kid, name: {}, score: {}, age:{}'.format(self.name, self.score, self.age)
複製代碼
爲了方便觀察打印結果,咱們重載了__repr__方法,能夠簡單地將它當作是Java當中的toString方法,這樣咱們能夠指定在print它的時候的輸出結果。
一樣,operator當中也提供了對象的排序因子函數,用法上和itemgetter同樣,只是名字不一樣。
from operator import attrgetter
kids = [Kid('xiaoming', 99, 12), Kid('xiaohong', 75, 13), Kid('xiaowang', 88, 15)]
sorted(kids, key=attrgetter('score'))
複製代碼
咱們也可使用匿名函數lambda來實現:
sorted(kids, key=lambda x: x.score)
複製代碼
到這裏尚未結束,由於仍然存在一些問題解決不了。雖然咱們實現了多關鍵字排序,可是還有一個問題解決不了,就是排序的順序問題。
咱們能夠在sorted函數的參數當中傳入reverse=True來控制是正序仍是倒敘,可是若是我使用多關鍵字,想要按照某個關鍵字升序,某個關鍵字降序怎麼辦?舉個例子,好比說咱們想要按照分數降序,年齡升序就沒辦法經過reverse來解決了,這就是當前解決不了的問題。
那應該怎麼辦呢?
這個時候就須要終極排序殺器上場了,也就是標題當中所說的自定義排序。也就是說咱們本身實現一個定義元素大小的函數,而後讓sorted來調用咱們這個函數來完成排序。這也是C++和Java等語言的用法。
自定義的函數並不難寫,咱們隨手就來:
def cmp(kid1, kid2):
return kid1.age < kid2.age if kid1.score == kid2.score else kid1.score > kid2.score
複製代碼
若是看不明白,也不要緊,我寫成完整版:
def cmp(kid1, kid2):
if kid1.score == kid2.score:
return kid1.age < kid2.age
else:
return kid1.score > kid2.score
複製代碼
寫完了以後,尚未結束,這個函數是不能直接投入使用的,他和咱們以前提到的lambda匿名函數是不同的。以前的匿名函數只是用來指定字段的,因此咱們不能直接將這個函數傳遞給key,還須要在外面包一層加工處理才能夠。不過這一層處理函數Python也已經有現成的工具了,咱們能夠直接調用,它在functools裏,咱們來看代碼:
from functools import cmp_to_key
sorted(kids, key=cmp_to_key(cmp))
複製代碼
咱們來看一下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
複製代碼
咱們能夠看到,在函數內部,它其實定義了一個類,而後在類當中重載了比較函數,最後返回的是一個重載了比較函數的新的對象。這些__lt__, __gt__函數就是類當中重載的比較函數。好比__lt__是小於的判斷函數,__eq__是相等的函數。那麼問題來了,咱們能不能直接在Kid類當中重載比較函數呢,這樣就能夠直接排序了。
答案是肯定的,咱們固然能夠這麼辦,實際上這也是面向對象當中很是經常使用的作法。相比於自定義比較函數,咱們每每更傾向於在類當中定義好優先級。Python當中實現的方法也很簡單,就是咱們手動實現一個__lt__函數,sorted默認會將小的元素排在前面,因此咱們只用實現__lt__一個函數就夠了。這個函數當中傳入的參數是另外一個對象,咱們直接在函數裏面寫清楚比較邏輯就好了。返回True表示當前對象比other小,不然比other大。
咱們附上完整代碼:
class Kid:
def __init__(self, name, score, age):
self.name = name
self.score = score
self.age = age
def __repr__(self):
return 'Kid, name: {}, score: {}, age:{}'.format(self.name, self.score, self.age)
def __lt__(self, other):
return self.score > other.score or (self.score == other.score and self.age < other.age)
複製代碼
實現了比較函數以後,咱們直接調用sorted,不用任何其餘傳參就能夠對它進行排序了。
今天的內容雖然難度不大,可是在咱們平常編程當中很是經常使用,常常會出現須要對複雜的對象和內容進行排序的狀況,因此但願你們都掌握,由於必定會派上用場的。
今天的文章就是這些,若是以爲有所收穫,請順手掃碼點個關注吧,大家的舉手之勞對我來講很重要。