Python排序傻傻分不清?一文看透sorted與sort用法

nintyuui發表於2021-09-11

Python排序傻傻分不清?一文看透sorted與sort用法

使用sorted()排序值

開始使用Python排序,首先要了解如何對數字資料和字串資料進行排序。

1. 排序數字型資料

可以使用Python透過sorted()對列表進行排序。比如定義了一個整數列表,然後使用numbers變數作為引數呼叫sorted():

>>> numbers = [6, 9, 3, 1]
>>> sorted(numbers)
[1, 3, 6, 9]
>>> numbers
[6, 9, 3, 1]

輸出是一個新的排序列表,如果列印原始變數時, 原始數字變數numbers未改變,因為sorted()只提供已排序的輸出,而不會更改原始值。這 意味著sorted()可以在列表中使用,將輸出立即分配給變數:

>>> numbers = [6, 9, 3, 1]
>>> numbers_sorted = sorted(numbers)
>>> numbers_sorted
[1, 3, 6, 9]
>>> numbers
[6, 9, 3, 1]

我們還可以透過呼叫sorted的help()來確認所有這些觀察結果。可選引數key和reverse將在本教程後面介紹:

>>> # Python 3
>>> help(sorted)
Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.

    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

像操作列表一樣,sorted()也可同樣地用於元組和集合:

>>> numbers_tuple = (6, 9, 3, 1)
>>> numbers_set = {5, 5, 10, 1, 0}
>>> numbers_tuple_sorted = sorted(numbers_tuple)
>>> numbers_set_sorted = sorted(numbers_set)
>>> numbers_tuple_sorted
[1, 3, 6, 9]
>>> numbers_set_sorted
[0, 1, 5, 10]

注意到,即使輸入一個集合和一個元組,輸出也是一個列表,因為sorted()按定義返回一個新列表。如果需要匹配輸入型別,則可以將返回的物件強制轉換為新型別。如果嘗試將結果列表強制轉換回集合,結果將是無序的,因為集合是無序的,如下:

>>> numbers_tuple = (6, 9, 3, 1)
>>> numbers_set = {5, 5, 10, 1, 0}
>>> numbers_tuple_sorted = sorted(numbers_tuple)
>>> numbers_set_sorted = sorted(numbers_set)
>>> numbers_tuple_sorted
[1, 3, 6, 9]
>>> numbers_set_sorted
[0, 1, 5, 10]
>>> tuple(numbers_tuple_sorted)
(1, 3, 6, 9)
>>> set(numbers_set_sorted)
{0, 1, 10, 5}

2. 排序字串型資料

字串型別與其他可迭代物件類似,如列表和元組。下面的示例顯示了sorted()如何將傳遞給它的字串進行遍歷,並在輸出中對每個字元進行排序:

>>> string_number_value = '34521'
>>> string_value = 'I like to sort'
>>> sorted_string_number = sorted(string_number_value)
>>> sorted_string = sorted(string_value)
>>> sorted_string_number
['1', '2', '3', '4', '5']
>>> sorted_string
[' ', ' ', ' ', 'I', 'e', 'i', 'k', 'l', 'o', 'o', 'r', 's', 't', 't']

sorted()將字串視為列表並遍歷每個元素。在字串中,每個元素表示字串中的一個字元,sorted會以相同的方式處理一個字串,對每個字元進行排序,包括空格。 .

我們透過使用split()可以改變輸出是單個字元的結果,以空格為邊界將原始字串拆分成幾個單詞,再透過.join()將幾個單詞重新組合在一起成為新的字串,具體如下:

>>> string_value = 'I like to sort'
>>> sorted_string = sorted(string_value.split())
>>> sorted_string
['I', 'like', 'sort', 'to']
>>> ' '.join(sorted_string)
'I like sort to'

Python排序的侷限性和陷阱

當使用Python對整數值進行排序時,可能會出現一些限制和奇怪的現象。

1. 具有不能比較資料型別的列表無法進行排序

有些資料型別使用sorted是無法進行比較的,因為它們的型別不同。如果嘗試在包含不可比較資料的列表上使用sorted(),Python將返回錯誤。在此示例中,由於不相容性,無法對同一列表中的None和int進行排序:

>>> mixed_types = [None, 0]
>>> sorted(mixed_types)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'int' and 'NoneType'

此錯誤顯示了為什麼Python無法對給定的值進行排序。它試圖透過使用小於運算子(<)來確定值,以確定排序順序中哪個值較低。 例如,數字1應該出現在蘋果這個詞之前嗎?但是,如果迭代器包含所有數字的整數和字串的組合,則可以使用列表推導將它們強制轉換為可比較的資料型別:

>>> mixed_numbers = [5, "1", 100, "34"]
>>> sorted(mixed_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'
>>> # List comprehension to convert all values to integers
>>> [int(x) for x in mixed_numbers]
[5, 1, 100, 34]
>>> sorted([int(x) for x in mixed_numbers])
[1, 5, 34, 100]

mixed_numbers中的每個元素都呼叫了int()來將任何str值轉換為int值。然後呼叫sorted()併成功比較每個元素並提供排序的輸出。

另外,Python還可以隱式地將值轉換為另一種型別。在下面的示例中,1 <= 0的評估是false語句,因此評估的輸出將為False。數字1可以轉換為True作為bool型別,而0轉換為False。

即使列表中的元素看起來不同,它們也可以全部轉換為布林值(True或False)並使用sorted()進行相互比較:

>>> similar_values = [False, 0, 1, 'A' == 'B', 1 <= 0]
>>> sorted(similar_values)
[False, 0, False, False, 1]

'A'=='B'和1 <= 0轉換為False並在有序輸出中返回。

此示例說明了排序的一個重要方面: 排序穩定性。 在Python中,當你對相等的值進行排序時,它們將在輸出中保留其原始順序。即使1移動,所有其他值都相等,它們保持相對於彼此的原始順序。在下面的示例中,所有值都被視為相等,並將保留其原始位置:

>>> false_values = [False, 0, 0, 1 == 2, 0, False, False]
>>> sorted(false_values)
[False, 0, 0, False, 0, False, False]

如果檢查原始順序和排序輸出,可以看到1 == 2轉換為False,所有排序輸出都是原始順序。

2. 當排序字串時,大小寫很重要

sorted()可用於字串列表,以按升序對值進行排序,預設情況下按字母順序排列:

>>> names = ['Harry', 'Suzy', 'Al', 'Mark']
>>> sorted(names)
['Al', 'Harry', 'Mark', 'Suzy']

但是,Python使用每個字串中第一個字母的Unicode程式碼點來確定升序排序。意思是sorted()不會將名稱Al和al視為相同。此示例使用ord()返回每個字串中第一個字母的Unicode程式碼點:

>>> names_with_case = ['harry', 'Suzy', 'al', 'Mark']
>>> sorted(names_with_case)
['Mark', 'Suzy', 'al', 'harry']
>>> # 每個word中第一個字母的unicode程式碼點列表推導式
>>> [(ord(name[0]), name[0]) for name in sorted(names_with_case)]
[(77, 'M'), (83, 'S'), (97, 'a'), (104, 'h')]

name [0]返回sorted(names_with_case)的每個元素中的第一個字元,ord()提供Unicode程式碼點。即使a在字母表中的M之前,M的程式碼點在a之前,因此排序的輸出首先是M。 如果第一個字母相同,則sorted()將使用第二個字元來確定順序,第三個字元等,依此類推,一直到字串的結尾:

>>> very_similar_strs = ['hhhhhd', 'hhhhha', 'hhhhhc','hhhhhb']
>>> sorted(very_similar_strs)
['hhhhha', 'hhhhhb', 'hhhhhc', 'hhhhhd']

除最後一個字元外,very_similar_strs的每個值都相同。 sorted()比較字串,因為前五個字元相同,輸出將基於第六個字元。

包含相同值的字串將最終排序為最短到最長,因為較短的字串沒有要與較長字串進行比較的元素:

>>> different_lengths = ['hhhh', 'hh', 'hhhhh','h']
>>> sorted(different_lengths)
['h', 'hh', 'hhhh', 'hhhhh']

最短的字串h排序第一,最長的字串hhhhh排序最後。

用reverse引數使用sorted()

如sorted()的help()文件所示,有一個名為reverse的可選關鍵字引數,它將根據分配給它的布林值更改排序行為。如果將reverse指定為True,則排序將按降序排列:

>>> names = ['Harry', 'Suzy', 'Al', 'Mark']
>>> sorted(names)
['Al', 'Harry', 'Mark', 'Suzy']
>>> sorted(names, reverse=True)
['Suzy', 'Mark', 'Harry', 'Al']

排序邏輯保持不變,這意味著名稱仍按其第一個字母排序。但是,如果reverse關鍵字設定為True,則輸出反轉。

如果指定了False,則排序將保持升序。可以使用前面的任何示例來使用True或False來檢視reverse的行為:

>>> names_with_case = ['harry', 'Suzy', 'al', 'Mark']
>>> sorted(names_with_case, reverse=True)
['harry', 'al', 'Suzy', 'Mark']
>>> similar_values = [False, 1, 'A' == 'B', 1 <= 0]
>>> sorted(similar_values, reverse=True)
[1, False, False, False]
>>> numbers = [6, 9, 3, 1]
>>> sorted(numbers, reverse=False)
[1, 3, 6, 9]

sorted()使用key引數排序

sorted()最強大的功能之一是一個叫做key的關鍵字引數。此引數需要將函式傳遞給它,並且該函式將用於要排序的列表中的每個值,以確定生成的順序。

我們假設排序一個特定列表的要求是列表中字串的長度,最短到最長。返回字串長度len()的函式將與key引數一起使用:

>>> word = 'paper'
>>> len(word)
5
>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=len)
['pie', 'book', 'banana', 'Washington']

生成的順序是一個字串順序最短到最長的列表。列表中每個元素的長度由len確定,然後以升序返回。

回到前面的例子,當大小寫不同時按第一個字母排序。key可以透過將整個字串轉換為小寫來解決該問題:

>>> names_with_case = ['harry', 'Suzy', 'al', 'Mark']
>>> sorted(names_with_case)
['Mark', 'Suzy', 'al', 'harry']
>>> sorted(names_with_case, key=str.lower)
['al', 'harry', 'Mark', 'Suzy']

輸出值尚未轉換為小寫,因為key不會處理原始列表中的資料。在排序期間,傳遞給key的函式將在每個元素上呼叫以確定排序順序,但原始值仍將體現在輸出中。 使用帶有key引數的函式時,有兩個主要限制。

首先,傳遞給key的函式中引數的數量必須為1。

下面的示例顯示了帶有兩個引數的加法函式的定義。當該函式用於數字列表中的鍵時,它會失敗,因為它缺少第二個引數。每次在排序期間呼叫add()時,它一次只從列表中接收一個元素:

>>> def add(x, y):
...     return x + y
... 
>>> values_to_add = [1, 2, 3]
>>> sorted(values_to_add, key=add)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add() missing 1 required positional argument: 'y'

第二個限制是與key一起使用的函式必須能夠處理iterable中的所有值。 例如,有一個數字列表,表示為要在sorted中使用的字串,而key將嘗試將它們轉換為使用int。如果iterable中的值不能轉換為整數,則該函式將失敗:

>>> values_to_cast = ['1', '2', '3', 'four']
>>> sorted(values_to_cast, key=int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'four'

作為字串的每個數值都可以轉換為int,但是four不能。這會導致引發ValueError並解釋four無法轉換為int,因為它無效。

key功能非常強大,因為幾乎任何內建或使用者定義的函式都可用於操作輸出順序。

如果排序要求是按每個字串中的最後一個字母排序可迭代(如果字母相同,然後使用下一個字母),則可以定義函式,然後在排序中使用。下面的示例定義了一個函式,該函式反轉傳遞給它的字串,然後該函式用作鍵的引數:

>>> def reverse_word(word):
...     return word[::-1]
...
>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=reverse_word)
['banana', 'pie', 'book', 'Washington']

word[::-1]切片語法用於反轉字串。每個元素都會應用reverse_word(),排序順序將基於後向單詞中的字元。

當然,也可以使用key引數中定義的lambda函式,而不是編寫獨立函式。 lambda匿名函式:1)必須內聯定義;2)沒有名字;3)不能包含statement;4)像函式一樣執行。

在下面的示例中,key被定義為沒有名稱的lambda,lambda採用的引數是x,然後x [:: -1]是將對引數執行的操作:

>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=lambda x: x[::-1])
['banana', 'pie', 'book', 'Washington']

在每個元素上呼叫x [::-1]並反轉該單詞。然後將反轉的輸出用於排序,但仍返回原始單詞。 如果需求發生變化,要求順序也應該反轉,那麼reverse關鍵字可以與key引數一起使用:

>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=lambda x: x[::-1], reverse=True)
['Washington', 'book', 'pie', 'banana']

當需要基於屬性對類物件進行排序時,lambda函式也很有用。如果有一組學生並需要按最終成績(從最高到最低)對其進行排序,則可以使用lambda從該課程中獲取成績屬性:

>>> from collections import namedtuple

>>> StudentFinal = namedtuple('StudentFinal', 'name grade')
>>> bill = StudentFinal('Bill', 90)
>>> patty = StudentFinal('Patty', 94)
>>> bart = StudentFinal('Bart', 89)
>>> students = [bill, patty, bart]
>>> sorted(students, key=lambda x: getattr(x, 'grade'), reverse=True)
[StudentFinal(name='Patty', grade=94), StudentFinal(name='Bill', grade=90), StudentFinal(name='Bart', grade=89)]

此示例使用namedtuple生成具有name和grade屬性的類。 lambda在每個元素上呼叫getattr()並返回grade的值。 reverse設定為True以使升序輸出轉為降序,以便首先排序最高等級。

當在sorted()上同時使用key和reverse關鍵字引數時,如何進行排序的可能性是無窮無盡的。當對一個小函式使用基本lambda時,程式碼可以保持乾淨和簡短,或者可以編寫一個全新的函式匯入,並在key引數中使用它。

使用.sort()排序值

名稱相似的.sort()與sorted()內建函式有著很大的不同。雖然它們或多或少都可以完成相同的事情,但list.sort()的help()文件突出顯示了.sort()和sorted()之間最重要的兩個區別:

>>> # Python2
Help on method_descriptor:

sort(...)
    L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;
    cmp(x, y) -> -1, 0, 1

>>> # Python3
>>> help(list.sort)
Help on method_descriptor:

sort(...)
    L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*

首先,sort是列表類的一種方法,只能與列表一起使用。它不是傳遞給它的迭代的內建函式。其次,sort返回None並修改值。我們來看看程式碼中這兩種差異的影響:

>>> values_to_sort = [5, 2, 6, 1]
>>> # 嘗試呼叫像使用sorted()呼叫sort()
>>> sort(values_to_sort)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'sort' is not defined

>>> # 嘗試在一個元組上使用 .sort()
>>> tuple_val = (5, 1, 3, 5)
>>> tuple_val.sort()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'sort'

>>> # 排序列表並且賦值給新的變數
>>> sorted_values = values_to_sort.sort()
>>> print(sorted_values)
None
>>> # 列印原始變數
>>> print(values_to_sort)
[1, 2, 5, 6]

與此程式碼示例中的sorted()相比,sort()操作的方式有一些非常顯著的差異:

1. sort()不產生有序輸出,因此對新變數的賦值僅傳遞None型別。

2. values_to_sort列表已就地更改,並且不以任何方式維持原始順序。

這些差異使得.sort()和sorted()絕對不能在程式碼中互換,如果以錯誤的方式使用它們,它們會產生意想不到的結果。

.sort()具有相同的key 和reverse可選關鍵字引數,這些引數產生與sorted()相同的強大功能。在這裡,可以按第三個單詞的第二個字母對短語列表進行排序,然後反向返回列表:

>>> phrases = ['when in rome', 
...     'what goes around comes around', 
...     'all is fair in love and war'
...     ]
>>> phrases.sort(key=lambda x: x.split()[2][1], reverse=True)
>>> phrases
['what goes around comes around', 'when in rome', 'all is fair in love and war']

何時使用sorted和.sort?

我們已經看到了sorted()和.sort()之間的區別,但我們什麼時候使用?該使用哪個?

假設有一場5k比賽即將舉行:第一屆年度Python 5k。需要獲取和分類來自比賽的資料,參賽者的號碼和完成比賽所需的秒數:

>>> from collections import namedtuple
>>> Runner = namedtuple('Runner', 'bibnumber duration')

當參賽者越過終點線時,每個參賽者將被新增到名為參賽者的列表中。在5k比賽中,並非所有參賽者同時越過起跑線,所以第一個越過終點線的人可能實際上不是最快的人:

>>> runners = []
>>> runners.append(Runner('2528567', 1500))
>>> runners.append(Runner('7575234', 1420))
>>> runners.append(Runner('2666234', 1600))
>>> runners.append(Runner('2425234', 1490))
>>> runners.append(Runner('1235234', 1620))
>>> # Thousands and Thousands of entries later...
>>> runners.append(Runner('2526674', 1906))

每次參賽者越過終點線時,他們的號碼號和他們的總持續時間(以秒為單位)都會新增到跑步者。

現在,負責處理結果資料的盡職程式設計師看到了這個列表,知道前5名最快的參與者是獲得獎品的獲勝者,剩下的參賽者將按最快的時間進行排序。

賽事中沒有提到透過不同屬性進行多型別的排序要求,也沒有提到將列表在某處儲存,只需按持續時間排序並獲取持續時間最短的五個參與者:

>>> runners.sort(key=lambda x: getattr(x, 'duration'))
>>> top_five_runners = runners[:5]

程式設計師選擇在key引數中使用lambda來獲取每個參賽者的duration屬性,並使用.sort()對執行程式進行排序。在對參賽者進行排序後,前5個元素儲存在top_five_runners中。

比賽總監過來告訴程式設計師,由於目前釋出的Python是3.7,他們決定每隔37位越過終點線的人將獲得一個免費的健身包。這時候,程式設計師開始出汗,因為參賽者名單已被不可逆轉地改變。沒有辦法按照他們完成的順序恢復原始的參賽者名單,並找到這些人。

如果你正在處理重要資料,甚至可能需要恢復原始資料,那麼.sort()不是最佳選擇。相反,如果資料是副本,是不重要的工作資料,或者沒有人會在意失不失去它,那麼.sort()可以是一個很好的選擇。

因此,可以用sorted(),使用相同的lambda對runners進行排序:

>>> runners_by_duration = sorted(runners, key=lambda x: getattr(x, 'duration'))
>>> top_five_runners = runners_by_duration[:5]

在這個帶有sorted()的場景中,原始的參賽者列表仍然完好無損並且沒有被覆蓋,並且每三十七人越過終點線的即興要求可以透過與原始值互動來完成:

>>> every_thirtyseventh_runners = runners[::37]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3209/viewspace-2837085/,如需轉載,請註明出處,否則將追究法律責任。

相關文章