Python應用——自定義排序全套方案

今天的這篇文章和大家聊聊Python當中的排序,和很多高級語言一樣,Python封裝了成熟的排序函數。我們只需要調用內部的sort函數,就可以完成排序。但是實際場景當中,排序的應用往往比較複雜,比如對象類型,當中有多個字段,我們希望按照指定字段排序,或者是希望按照多關鍵字排序,這個時候就不能簡單的函數調用來解決了。


字典排序


我們先來看下最常見的字典排序的場景,假設我們有一個字典的數組,字典內有多個字段。我們希望能夠根據字典當中的某一個字段來進行排序,我們用實際數據來舉個例子:


Python應用——自定義排序全套方案


這裡的kids是一個dict類型的數組,dict當中擁有name, score和age三個字段。假設我們當下希望能夠按照score來排序,應該怎麼辦呢?


對於這個問題,解決的方案有很多,首先,我們可以使用上一篇文章當中提到的匿名函數來指定排序的。這裡的用法和上篇文章優先隊列的用法是一樣的,我們直接來看代碼:


Python應用——自定義排序全套方案


在匿名函數當中我們接收的x是kids當中的元素,也就是一個dict,所以我們想要指定我們希望的字段,需要用dict訪問元素的方法,也就是用中括號來查找對應字段的值。


假如我們希望按照多關鍵字排序呢?


首先介紹一下多關鍵字排序,還是用上面的數據打比方。在上面的例子當中,各個kid的score都不一樣,所以排序的結果是確定的。但如果存在兩個人的score相等,我希望年齡小的排在前面,那麼應該怎麼辦呢?我們分析一下可以發現,原本是按照分數從小到大排序,但有可能會出現分數相等的情況。這個時候,我們希望能夠按照在分數相等的情況下來比較年齡,也就是說我們希望根據兩個關鍵字來排序,第一個關鍵字是分數,第二個關鍵字是年齡。


由於Python當中支持tuple和list類型的排序,也就是說我們可以直接比較[1, 3]和[1, 2]的大小關係,Python會自動一次比較兩個數組當中的元素的大小。如果相等就自動往後比較,直到出現不等或者結束為止。


明白了這點,其實就很好辦了。我們只要在匿名函數當中稍稍修改,讓它返回的結果增加一個字段即可。


Python應用——自定義排序全套方案


itemgetter


除了匿名函數,Python也有自帶的庫可以解決這個問題。用法和匿名函數非常接近,使用起來稍稍容易一些。


它就是operator庫當中的itemgetter函數,我們直接來看代碼:


Python應用——自定義排序全套方案


如果是多關鍵字也可以,傳入多個key即可:


Python應用——自定義排序全套方案


對象排序


我們接下來看一下對象的自定義排序,我們首先把上面的dict寫成對象:


Python應用——自定義排序全套方案


為了方便觀察打印結果,我們重載了__repr__方法,可以簡單地將它當做是Java當中的toString方法,這樣我們可以指定在print它的時候的輸出結果。


同樣,operator當中也提供了對象的排序因子函數,用法上和itemgetter一樣,只是名字不同。


Python應用——自定義排序全套方案


我們也可以使用匿名函數lambda來實現:


Python應用——自定義排序全套方案


自定義排序


到這裡還沒有結束,因為仍然存在一些問題解決不了。雖然我們實現了多關鍵字排序,但是還有一個問題解決不了,就是排序的順序問題。


我們可以在sorted函數的參數當中傳入reverse=True來控制是正序還是倒敘,但是如果我使用多關鍵字,想要按照某個關鍵字升序,某個關鍵字降序怎麼辦?舉個例子,比如說我們想要按照分數降序,年齡升序就沒辦法通過reverse來解決了,這就是當前解決不了的問題。


那應該怎麼辦呢?


這個時候就需要終極排序殺器上場了,也就是標題當中所說的自定義排序。也就是說我們自己實現一個定義元素大小的函數,然後讓sorted來調用我們這個函數來完成排序。這也是C++和Java等語言的用法。


自定義的函數並不難寫,我們隨手就來:


Python應用——自定義排序全套方案


如果看不明白,也沒關係,我寫成完整版:


Python應用——自定義排序全套方案


寫完了之後,還沒有結束,這個函數是不能直接投入使用的,它和我們之前提到的lambda匿名函數是不一樣的。之前的匿名函數只是用來指定字段的,所以我們不能直接將這個函數傳遞給key,還需要在外面包一層加工處理才可以。不過這一層處理函數Python也已經有現成的工具了,我們可以直接調用,它在functools裡,我們來看代碼:


Python應用——自定義排序全套方案


我們來看一下cmp_to_key函數里的源碼:


Python應用——自定義排序全套方案


我們可以看到,在函數內部,它其實定義了一個類,然後在類當中重載了比較函數,最後返回的是一個重載了比較函數的新的對象。這些__lt__, __gt__函數就是類當中重載的比較函數。比如__lt__是小於的判斷函數,__eq__是相等的函數。那麼問題來了,我們能不能直接在Kid類當中重載比較函數呢,這樣就可以直接排序了。


答案是確定的,我們當然可以這麼辦,實際上這也是面向對象當中非常常用的做法。相比於自定義比較函數,我們往往更傾向於在類當中定義好優先級。Python當中實現的方法也很簡單,就是我們手動實現一個__lt__函數,sorted默認會將小的元素排在前面,所以我們只用實現__lt__一個函數就夠了。這個函數當中傳入的參數是另一個對象,我們直接在函數里面寫清楚比較邏輯就行了。返回True表示當前對象比other小,否則比other大。


我們附上完整代碼:


Python應用——自定義排序全套方案


實現了比較函數之後,我們直接調用sorted,不用任何其他傳參就可以對它進行排序了。


今天的內容雖然難度不大,但是在我們日常編程當中非常常用,經常會出現需要對複雜的對象和內容進行排序的情況,所以希望大家都掌握,因為一定會派上用場的。


今天的文章就是這些,如果覺得有所收穫,請順手點個關注或轉發吧,你們的舉手之勞對我來說很重要。


分享到:


相關文章: