在Python中如何使用sorted()和sort()函數
在某種程度上,所有的程序員都必須編寫代碼對項目或數據進行排序。在應用程序當中,排序對於用戶體驗而言是非常重要的,無論是按時間戳對用戶最近的活動進行排序,還是按照姓氏的字母順序排列電子郵件收件人列表。Python的排序功能十分強大,可以在粒度級別上進行基本排序或自定義排序。
在本教程中,你將會學習如何在不同的數據結構中對各種各樣的數據類型進行排序和自定義排序,並使用Python中兩種不同的排序方法。
在這篇教程結束時,你將會知道如何:
1、在數據結構上完成基本的Python排序
2、區分sorted()和.sort()函數
3、基於特定的要求在編碼中自定義一個複雜的排序
對於本篇教程而言,你需要對列表、元組以及集合有一個基礎的理解。在本篇教程中,將會使用到這些數據結構,並且在這些數據結構上將會進行一些基礎的操作。而且,這篇教程使用的是Python3的版本,所以如果你使用的是Python2的版本,輸出樣例可能會稍微有些差別。
使用sorted()函數排序
在開始使用Python進行排序之前,首先你要了解如何對數值和字元串數據進行排序。
對數值進行排序
你可以使用Python中的sorted()函數對一個列表進行排序。在本例中,定義了一個整數列表,然後調用sorted()函數,可變的numbers作為sorted()函數的參數。
輸出結果是一個新的,有序的列表。當列印原始變數時,我們可以知道初始值並沒有發生改變。
這個例子展示了sorted()函數四種重要的特性:
1.sorted()函數不需要定義。它是一個內置函數,可以在標準的Python安裝中使用。
2.在沒有額外的參數的情況下,sorted()函數按照升序對值進行排列,也就是按照從小到大的順序。
3.原始的numbers不會改變,因為sorted()函數提供了一個新的有序的輸出結果,並且不改變原始值的順序。
4.當sorted()函數被調用時,它會提供一個有序的列表作為返回值。
最後一點意味著列表可以使用sorted()函數,並且輸出結果可以立刻賦值給一個變數:
在這個例子中,有一個新的變數numbers_sorted存儲了sorted()函數的輸出結果。
你可以通過調用help()函數來查看sorted()函數以確認所有的這些觀察結果。可選參數key和reverse將在本教程後面介紹:
技術細節:如果你正在從Python2過渡到Python3,並且對它的同名函數非常熟悉,你應該注意Python3中的幾個重要變化:
1.Python3中的sorted()函數沒有cmp參數。相反,只使用key參數來引入自定義排序邏輯。
2.key和reverse必須作為參數傳遞,這與Python2不同,在Python2中它們可以作為位置參數傳遞。
如果需要將Python2的cmp函數轉換為key函數,請查看functools.cmp_to_key()。本教程將不介紹使用Python2的任何示例。
元組和集合同樣可以使用sorted()函數:
值得注意的是即使輸入的是一個集合和元組,輸出結果仍然是一個列表,因為sorted()函數根據定義會返回一個新列表。如果返回的對象需要匹配輸入類型,則可以將其轉化為新類型。如果試圖將結果列錶轉換回集合類型,請注意,按照定義而言,集合是無序的:
正如預料的結果一樣,當把結果列錶轉換為集合時,numbers_set_sorted是無序的。其它的變數,如numbers_tuple_sorted保留了排序後的順序。
對字元串進行排序
str類型的排序類似於列表和元組等其它可迭代對象。下面的例子展示了sorted()函數如何遍歷傳遞給它的值中的每個字元並在輸出中對字元進行排序:
sorted()函數將一個str看作一個列表,並遍歷其中的每一個元素。在一個str中,每一個元素都對應著str中的一個字元。sorted()函數以相同的方式對待每一個句子,它會對每個字元包括空格進行排序。
.split()可以改變這個結果並清理輸出,.join()可以將所有內容重新連接在一起。我們將會簡單介紹輸出的特定順序以及為什麼是這樣:
在本例中,原句被轉換為一個單詞列表而不是作為一個str。然後,對該列表進行排序並再次組合形成一個str而不是一個列表。
Python排序的局限性和陷阱
值得注意的是,當你使用Python對非整數類型的值進行排序時,可能會出現一些限制和奇怪的結果。
含有不可比較數據類型的列表無法使用sorted()函數
有些數據類型不能使用sorted()函數相互比較,因為它們太不一樣了。如果你試圖對一個含有不可比較數據類型的列表使用sorted()函數,Python將會返回一個錯誤。在本例當中,同一個列表中的None和int不能排序,因為它們是不兼容的:
這個錯誤說明了為什麼Python不能對給定的值進行排序。它試圖通過使用小於操作符(<)來確定哪個值更低,從而將值按順序排列。
當你試圖在不使用sorted()函數的情況下比較兩個不可比較值時,Python會拋出相同的類型錯誤。
如果列表中的值可以比較並且不會拋出類型錯誤,那麼這個列表就可以進行排序。這可以防止對具有本質上不可排序值的迭代器進行排序,並生成可能沒有意義的輸出。
例如,數字1應該放在單詞apple之前嗎?然而,如果一個iterable包含整數和字元串的組合,並且它們都是數字,那麼可以使用列表將它們轉換為可比較的數據類型:
mixed_numbers中的每一個元素都調用了int()函數,可以將任何str值轉化為int值。然後,調用sorted()函數就可以成功比較每一個元素,併產生一個排序的結果。
Python還可以隱式地將值轉換為另一種類型。在下面的例子中,1<=0的值為False,所以輸出結果就是False。作為布爾類型時,數字1可以轉化為True,數字0可以轉化為False。
即使列表中的元素看起來都不相同,但是它們都可以轉化為布爾值(True或False),並使用sorted()函數相互比較:
"A"=="B"和1<=0都被轉化為了布爾值False,並且返回了一個有序的輸出結果。
這個例子說明了一個排序的重要方面:排序穩定性。在Python當中,當你對相等的值進行排序時,它們將會在輸出時保留原本的順序。即使移動了1的位置,所有其他的值都是相等的,因此它們保持了相對於彼此的原始順序。在下面的例子中,所有的值都是相等的,並且將會保持它們原本的位置:
如果你觀察原始的順序和輸出的順序,你將會發現1 == 2被轉化成了False,並且所有排序後的輸出都與原始順序相同。
當你對字元串排序時,大小寫很重要
sorted()函數可以按照升序對字元串列表的值進行排序,默認情況下按照字母順序:
然而,Python使用每個字元串中第一個字母的Unicode數值來確定升序排序順序。這意味著sorted()函數不會將名稱AL和al看作是一樣的。本例將會使用ord()函數返回每個字元串中第一個字母的Unicode數值:
name[0]會返回sorted(names_with_case)中每一個元素的第一個字元,ord()會提供其Unicode數值。即使在字母表中a在M之前,但是M的Unicode數值在a之前,所以排序的結果是M在前。
如果第一個字母是相同的,那麼sorted()將使用第二個字元來確定順序,如果第二個字元是相同的,將會使用第三個字元,以此類推,直到字元串的末尾:
除了最後一個字元外,very_similar_strs的每一個字元都是相同的。使用sorted()函數比較字元串時,由於前五個字元都是相同的,所以輸出結果將會根據第六個字元的值來判斷。
包含相同值的字元串最終的順序為從短到長,這是由於較短的字元串沒有可以與較長字元串相比較的元素:
最短的字元串"h"在第一位,最長的字元串"hhhhh"在最後一位。
使用含有reverse參數的sorted()函數
正如sorted()函數的help()文檔所示,有一個可選的參數reverse,它將根據分配給它的布爾值改變排序。如果reverse = True,那麼就會按照降序排列:
排序的邏輯仍然保持不變,這意味著這些名字仍然按照第一個字母排序,但是因為reverse關鍵字被設置為True,所以輸出結果的順序是相反的。
當reverse關鍵字設置為False時,順序將保持升序。之前的任何例子都可以用來檢查reverse關鍵字設置True或Fasle後的結果:
使用含有key參數的sorted()函數
參數key是sorted()函數最強大的組成部分之一。這個參數可以接收一個函數,該函數將作用於排序列表中的每個值,以確定結果的順序。
以一個簡單的例子為例,對一個特定的列表進行排序,我們假設列表中字元串的長度為排序的要求,由短到長。參數key被設置為len()函數,len()函數的功能是返回一個字元串的長度:
最後的結果是一個按字元串順序從短到長的列表。列表中每個元素的長度是由len()函數確定的,然後按照升序返回。
讓我們回到前面的例子,按照第一個字母排序,當出現大小寫不同的情況時,key可以通過將整個字元串轉換成小寫來解決這個問題:
輸出值沒有被轉化為小寫,這是因為參數key並沒有處理原始列表中的數據。在排序過程中,將對每個元素調用key函數來確定排序順序,但是輸出的仍是原始值。
當使用帶有參數key的函數時,有兩個主要限制。
第一,傳遞給key的函數中所需參數的數量必須為1。
下面的例子說明了加法函數的定義,它需要兩個參數。當加法函數作為參數key作於用一個數值列表時,它並不能發揮作用,因為它缺少第二個參數。在排序過程中每次調用add()函數,它每次只從列表中接收一個元素:
第二個限制是,帶有key的函數必須能夠處理迭代序列中的所有值。例如,你有一個以字元串形式表示的數值列表,要對其使用sorted()函數,參數key試圖使用int()函數將它們轉化為數字。如果迭代序列中的值不能轉換為整數,那麼該函數將無法產生作用:
每個作為str的數值都可以轉換為int,但是four不行。這會引起一個ValueError,其錯誤解釋為four不能被轉化為整形,因為它是無效的。
參數key的功能非常強大,因為幾乎所有函數,無論是內置函數還是用戶自定義函數,都可以用來控制輸出順序。
如果排序要求是按照每個字元串的最後一個字母對可迭代序列進行排序(如果最後一個字母是相同的,就使用倒數第二個字母),那麼就可以定義一個函數用來排序。下面的例子定義了一個函數,其功能是反轉字元串序列,然後將該函數作為參數傳遞給key:
word[::-1]用於反轉字元串。reverse_word()將會作用於每一個元素,而且排列順序將會取決於最後的字元。
你可以在參數中定義lambda函數來代替編寫一個獨立的函數。
lambda函數是一個匿名函數:
1.必須是內聯定義
2.沒有名稱
3.不能包含語句
4.像函數一樣執行
在下面的例子中,參數key被設置為一個沒有名稱的lambda函數,lambda的參數是x,x[::-1]是對參數執行的操作:
對每個元素調用x[::-1]並反轉單詞。反轉後的單詞被用於排序,但是返回的仍然是原始的單詞。
如果需求發生了變化,並且順序也應該顛倒,那麼reverse關鍵字可以和key參數一起使用:
當你需要根據屬性對類對象排序時,lambda函數也很有用。如果你有一組學生,需要根據他們的最終成績按照從高到低的順序對他們進行排序,那麼lambda可以用來從類中獲取grade屬性:
lambda在每個元素上調用getattr()函數並返回grade的值。
將reverse設置為True,使升序輸出變為為降序輸出,以便使成績最高的排在第一位。
當你同時使用sorted()函數中的key和reverse參數時,如何實現排序的可能性是無窮無盡的。當你為一個小函數使用基本lambda式時,代碼可以保持簡短和整潔,或者你可以編寫一個全新的函數,導入它,並在key參數中使用它。
使用.sort()對值排序
名稱非常相似的.sort()與內置的sorted()有很大的差別。它們或多或少得完成了相同的事情,但是list.sort()的help()文檔強調了二者之間最重要的兩個區別:
第一,sort是list類的一個方法,只能與list一起使用。它不是一個內置的迭代器。
第二,.sort()返回None並改變值的位置。讓我們看一下這兩種代碼差異的影響:
在這個代碼示例中,.sort()與sorted()的操作方式有一些非常顯著的差異:
1..sort()沒有有序的輸出,因此對新變數的賦值只傳遞None類型
2.values_to_sort列表的順序已經發生了改變,而且原始順序也並沒有以任何形式保留下來。
這些行為上的差異使得.sort()和sorted()在代碼中絕對不可互換,如果以錯誤的方式使用它們,可能會產生意想不到的結果。
.sort()具有與sorted()相同的key和reverse這種可選的關鍵字參數,這些參數具有與sorted()相同的強大的功能。在這裡,你可以根據第三個單詞的第二個字母對短語列表進行排序,然後逆序返回列表:
在本例當中,lambda函數被用來完成以下功能:
1.把每個短語分成一個單詞列表
2.找到本例中的第三個元素或單詞
3.找到第三單詞中的第二個字母
何時使用sorted()和.sort()
你已經看到了sorted()和.sort()之間的區別,但是什麼時候該用哪一個呢?
讓我來說一下,有一個5k比賽即將到來:第一屆年度Python 5k。需要捕獲並排序來自比賽的數據。需要捕獲的數據是跑步者的號碼和完成比賽所需的秒數:
當參賽者跨過終點線時,每一個Runner都會被加入一個名為runners的列表當中。在5k比賽中,並不是所有的運動員都同時跨過起跑線,所以第一個越過終點線的人可能並不是最快的:
每一次一個跑步者跨過終點線,他們的號碼以及耗時(以秒為單位)都會被加入到runners列表當中。
現在,負責處理結果數據的程序員看到了這個列表,知道了前5名最快的選手是獲獎者,其餘的參賽者將按時間排序。
不需要根據不同的屬性進行多種類型的排序。這個列表的大小是合理的。沒有提到將列表存儲在某個地方。只需要按時間排序,找出耗時最短的5名選手:
編程人員選擇在參數key上使用lambda函數,以便從每一個runner中獲取它們的持續時間屬性,並且使用.sort()對runners列表進行排序。在runners列表完成排序之後,前5個元素被存儲在top_five_runners列表中。
任務完成!比賽總監過來告訴程序員,由於Python的當前版本是3.7,所以他們決定每37名衝過終點線的人將獲得一個免費的健身包。
此時,程序員開始感到很苦惱,因為runners列表已經不可逆轉地更改了,沒有辦法恢復原始的runners列表的順序,並找到每37人。
如果您正在處理重要的數據,即使這些原始數據需要恢復的可能性很小,那麼.sort()也不是最佳選項。如果數據是副本,如果它是不重要的工作數據,如果沒有人介意丟失它,因為它可以被找回,那麼.sort()是一個不錯的選擇。
或者,runners列表可以使用sorted()函數排序,並且使用相同的lambda表達式:
在使用sorted()函數的這個方案中,原始的runners列表仍然是完整的,沒有被覆蓋。通過與原始值的交互,可以實現每隔37人到達終點線的臨時性要求:
every_thirtyseventh_runners列表是對runners列表使用列表切片語法創建的,它仍然包含跑步者越過終點線的原始順序。
怎樣使用Python排序:結論
.sort()和sorted()可以提供所需的排序順序,如果你正確地將它們與可選的關鍵字參數reverse和key一起使用的話。
當涉及到輸出和修改數據時,兩者具有非常不同的特性,因此請確保您仔細考慮過將使用.sort()的任何應用功能或程序,因為它會不可逆轉地覆蓋數據。
對於熱心尋找排序方面的挑戰的Python高手來說,可以嘗試在排序中使用更複雜的數據類型:嵌套迭代器。
英文原文:https://realpython.com/python-sort/
譯者:Lyx
※Python中的端對端主題建模: 隱含狄利克雷分布(LDA)
TAG:Python部落 |