Python教程:自定义排序全套方案
Python封裝了成熟的排序函數(shù)。我們只需要調(diào)用內(nèi)部的sort函數(shù),就可以完成排序。但是實際場景當(dāng)中,排序的應(yīng)用往往比較復(fù)雜,比如對象類型,當(dāng)中有多個字段,我們希望按照指定字段排序,或者是希望按照多關(guān)鍵字排序,這個時候就不能簡單的函數(shù)調(diào)用來解決了。
字典排序
我們先來看下最常見的字典排序的場景,假設(shè)我們有一個字典的數(shù)組,字典內(nèi)有多個字段。我們希望能夠根據(jù)字典當(dāng)中的某一個字段來進行排序,我們用實際數(shù)據(jù)來舉個例子:
kids = [{'name': 'xiaoming', 'score': 99, 'age': 12},{'name': 'xiaohong', 'score': 75, 'age': 13},{'name': 'xiaowang', 'score': 88, 'age': 15} ]這里的kids是一個dict類型的數(shù)組,dict當(dāng)中擁有name, score和age三個字段。假設(shè)我們當(dāng)下希望能夠按照score來排序,應(yīng)該怎么辦呢?
對于這個問題,解決的方案有很多,首先,我們可以使用上一篇文章當(dāng)中提到的匿名函數(shù)來指定排序的。這里的用法和上篇文章優(yōu)先隊列的用法是一樣的,我們直接來看代碼:
sorted(kids, key=lambda x: x['score'])在匿名函數(shù)當(dāng)中我們接收的x是kids當(dāng)中的元素,也就是一個dict,所以我們想要指定我們希望的字段,需要用dict訪問元素的方法,也就是用中括號來查找對應(yīng)字段的值。
假如我們希望按照多關(guān)鍵字排序呢?
首先介紹一下多關(guān)鍵字排序,還是用上面的數(shù)據(jù)打比方。在上面的例子當(dāng)中,各個kid的score都不一樣,所以排序的結(jié)果是確定的。但如果存在兩個人的score相等,我希望年齡小的排在前面,那么應(yīng)該怎么辦呢?我們分析一下可以發(fā)現(xiàn),原本是按照分?jǐn)?shù)從小到大排序,但有可能會出現(xiàn)分?jǐn)?shù)相等的情況。這個時候,我們希望能夠按照在分?jǐn)?shù)相等的情況下來比較年齡,也就是說我們希望根據(jù)兩個關(guān)鍵字來排序,第一個關(guān)鍵字是分?jǐn)?shù),第二個關(guān)鍵字是年齡。
由于Python當(dāng)中支持tuple和list類型的排序,也就是說我們可以直接比較[1, 3]和[1, 2]的大小關(guān)系,Python會自動一次比較兩個數(shù)組當(dāng)中的元素的大小。如果相等就自動往后比較,直到出現(xiàn)不等或者結(jié)束為止。
明白了這點,其實就很好辦了。我們只要在匿名函數(shù)當(dāng)中稍稍修改,讓它返回的結(jié)果增加一個字段即可。
sorted(kids, key=lambda x: (x['score'], x['age']))itemgetter
除了匿名函數(shù),Python也有自帶的庫可以解決這個問題。用法和匿名函數(shù)非常接近,使用起來稍稍容易一些。
它就是operator庫當(dāng)中的itemgetter函數(shù),我們直接來看代碼:
from operator import itemgettersorted(kids, key=itemgetter('score'))如果是多關(guān)鍵字也可以,傳入多個key即可:
sorted(kids, key=itemgetter('score', 'age'))對象排序
我們接下來看一下對象的自定義排序,我們首先把上面的dict寫成對象:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:778463939 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' class Kid:def __init__(self, name, score, age):self.name = nameself.score = scoreself.age = agedef __repr__(self):return 'Kid, name: {}, score: {}, age:{}'.format(self.name, self.score, self.age)為了方便觀察打印結(jié)果,我們重載了__repr__方法,可以簡單地將它當(dāng)做是Java當(dāng)中的toString方法,這樣我們可以指定在print它的時候的輸出結(jié)果。
同樣,operator當(dāng)中也提供了對象的排序因子函數(shù),用法上和itemgetter一樣,只是名字不同。
from operator import attrgetterkids = [Kid('xiaoming', 99, 12), Kid('xiaohong', 75, 13), Kid('xiaowang', 88, 15)]sorted(kids, key=attrgetter('score'))我們也可以使用匿名函數(shù)lambda來實現(xiàn):
sorted(kids, key=lambda x: x.score)自定義排序
到這里還沒有結(jié)束,因為仍然存在一些問題解決不了。雖然我們實現(xiàn)了多關(guān)鍵字排序,但是還有一個問題解決不了,就是排序的順序問題。
我們可以在sorted函數(shù)的參數(shù)當(dāng)中傳入reverse=True來控制是正序還是倒敘,但是如果我使用多關(guān)鍵字,想要按照某個關(guān)鍵字升序,某個關(guān)鍵字降序怎么辦?舉個例子,比如說我們想要按照分?jǐn)?shù)降序,年齡升序就沒辦法通過reverse來解決了,這就是當(dāng)前解決不了的問題。
那應(yīng)該怎么辦呢?
這個時候就需要終極排序殺器上場了,也就是標(biāo)題當(dāng)中所說的自定義排序。也就是說我們自己實現(xiàn)一個定義元素大小的函數(shù),然后讓sorted來調(diào)用我們這個函數(shù)來完成排序。這也是C++和Java等語言的用法。
自定義的函數(shù)并不難寫,我們隨手就來:
def cmp(kid1, kid2):return kid1.age < kid2.age if kid1.score == kid2.score else kid1.score > kid2.score如果看不明白,也沒關(guān)系,我寫成完整版:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:778463939 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' def cmp(kid1, kid2):if kid1.score == kid2.score:return kid1.age < kid2.ageelse:return kid1.score > kid2.score寫完了之后,還沒有結(jié)束,這個函數(shù)是不能直接投入使用的,他和我們之前提到的lambda匿名函數(shù)是不一樣的。之前的匿名函數(shù)只是用來指定字段的,所以我們不能直接將這個函數(shù)傳遞給key,還需要在外面包一層加工處理才可以。不過這一層處理函數(shù)Python也已經(jīng)有現(xiàn)成的工具了,我們可以直接調(diào)用,它在functools里,我們來看代碼:
from functools import cmp_to_keysorted(kids, key=cmp_to_key(cmp))我們來看一下cmp_to_key函數(shù)里的源碼:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:778463939 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' def cmp_to_key(mycmp):"""Convert a cmp= function into a key= function"""class K(object):__slots__ = ['obj']def __init__(self, obj):self.obj = objdef __lt__(self, other):return mycmp(self.obj, other.obj) < 0def __gt__(self, other):return mycmp(self.obj, other.obj) > 0def __eq__(self, other):return mycmp(self.obj, other.obj) == 0def __le__(self, other):return mycmp(self.obj, other.obj) <= 0def __ge__(self, other):return mycmp(self.obj, other.obj) >= 0__hash__ = Nonereturn K我們可以看到,在函數(shù)內(nèi)部,它其實定義了一個類,然后在類當(dāng)中重載了比較函數(shù),最后返回的是一個重載了比較函數(shù)的新的對象。這些__lt__,__gt__函數(shù)就是類當(dāng)中重載的比較函數(shù)。比如__lt__是小于的判斷函數(shù),__eq__是相等的函數(shù)。那么問題來了,我們能不能直接在Kid類當(dāng)中重載比較函數(shù)呢,這樣就可以直接排序了。
答案是確定的,我們當(dāng)然可以這么辦,實際上這也是面向?qū)ο螽?dāng)中非常常用的做法。相比于自定義比較函數(shù),我們往往更傾向于在類當(dāng)中定義好優(yōu)先級。Python當(dāng)中實現(xiàn)的方法也很簡單,就是我們手動實現(xiàn)一個__lt__函數(shù),sorted默認(rèn)會將小的元素排在前面,所以我們只用實現(xiàn)__lt__一個函數(shù)就夠了。這個函數(shù)當(dāng)中傳入的參數(shù)是另一個對象,我們直接在函數(shù)里面寫清楚比較邏輯就行了。返回True表示當(dāng)前對象比other小,否則比other大。
我們附上完整代碼:
class Kid:def __init__(self, name, score, age):self.name = nameself.score = scoreself.age = agedef __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)實現(xiàn)了比較函數(shù)之后,我們直接調(diào)用sorted,不用任何其他傳參就可以對它進行排序了。
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Python教程:自定义排序全套方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python序列的增量赋值
- 下一篇: websocket python爬虫_p