一文帶你解密Python可迭代對象的排序問題
假設(shè)有一個(gè)可迭代對象,現(xiàn)在想要對它內(nèi)部的元素進(jìn)行排序,我們一般會(huì)使用內(nèi)置函數(shù) sorted,舉個(gè)例子:
data?=?(3,?4,?1,?2,?5) print( ????sorted(data) )??#?[1,?2,?3,?4,?5] data?=?(3.14,?2,?1.75) print( ????sorted(data) )??#?[1.75,?2,?3.14] data?=?["satori",?"koishi",?"marisa"] print( ????sorted(data) )??#?['koishi',?'marisa',?'satori']
如果可迭代對象里面的元素是數(shù)值,那么會(huì)按照數(shù)值的大小進(jìn)行排序;如果是字符串,那么會(huì)按照字符串的字典序進(jìn)行排序,并且 sorted 函數(shù)會(huì)返回一個(gè)新的列表。
sorted 函數(shù)默認(rèn)是升序排序,如果想要降序,那么可以傳遞一個(gè)關(guān)鍵字參數(shù) reverse=True。
data?=?[(3,?4), ????????(3,?1), ????????(2,?3)] print( ????sorted(data) )??#?[(2,?3),?(3,?1),?(3,?4)]
如果可迭代對象里面都是元組的話,也是可以的,元組在比較大小的時(shí)候,會(huì)先按照元組的第一個(gè)元素比較;第一個(gè)元素相等,再按照第二個(gè)元素比較,依次類推。
因此在使用 sorted 函數(shù)的時(shí)候,可迭代對象內(nèi)部的元素,要滿足彼此之間都是可比較的,否則報(bào)錯(cuò)。
data?=?[123,?456, "123"] try: ????print(sorted(data)) except?TypeError?as?e: ????print(e) """ '<'?not?supported?between?instances?of?'str'?and?'int' """ data?=?[{"a":?1},?{"b":?2}] try: ????print(sorted(data)) except?TypeError?as?e: ????print(e) """ '<'?not?supported?between?instances?of?'dict'?and?'dict' """
我們看到,由于 data 里面存在不可比較的元素,因此報(bào)錯(cuò)了。那么問題來了,假設(shè)有這樣一個(gè)列表:
data?=?[{"name":?"satori",?"age":?17}, ????????{"name":?"marisa",?"age":?15}, ????????{"name":?"koishi",?"age":?16}] #?字典是不可比較大小的,因此直接使用?sorted?會(huì)報(bào)錯(cuò) #?我們希望按照字典內(nèi)部的?"age"?字段進(jìn)行排序 #?得到下面的結(jié)果 [{'name':?'marisa',?'age':?15}, ?{'name':?'koishi',?'age':?16}, ?{'name':?'satori',?'age':?17}]
如果是你的話,你會(huì)怎么做呢?很明顯,我們將每個(gè) "age" 字段的值取出來,和所在的字典拼接成一個(gè)元組(或列表)不就行了,然后對元組進(jìn)行排序,舉個(gè)例子:
data?=?[{"name":?"satori",?"age":?17}, ????????{"name":?"marisa",?"age":?15}, ????????{"name":?"koishi",?"age":?16}] data?=?[(d["age"],?d)?for?d?in?data] print(data) """ [(17,?{'name':?'satori',?'age':?17}),? ?(15,?{'name':?'marisa',?'age':?15}),? ?(16,?{'name':?'koishi',?'age':?16})] """ #?由于?data?內(nèi)部的元素是一個(gè)元組 #?所以排序的時(shí)候會(huì)按照元組的第一個(gè)元素排序 sorted_data?=?sorted(data) print(sorted_data) """ [(15,?{'name':?'marisa',?'age':?15}),? ?(16,?{'name':?'koishi',?'age':?16}),? ?(17,?{'name':?'satori',?'age':?17})] """ #?此時(shí)順序就排好了,然后再把字典取出來 sorted_data?=?[tpl[-1]?for?tpl?in?sorted_data] print(sorted_data) """ [{'name':?'marisa',?'age':?15},? ?{'name':?'koishi',?'age':?16},? ?{'name':?'satori',?'age':?17}] """
顯然這樣就實(shí)現(xiàn)了基于字典內(nèi)部某個(gè)字段的值,來對字典進(jìn)行排序,只不過上面的代碼還有一點(diǎn)點(diǎn)缺陷。我們說元組在比較的時(shí)候會(huì)先比較第一個(gè)元素,第一個(gè)元素相同的話,會(huì)比較第二個(gè)元素。
而我們上面 data 里面的元組,由于第一個(gè)元素都不相等,所以直接就比較出來了。但如果是下面這種情況呢?
data?=?[{"name":?"satori",?"age":?17}, ????????{"name":?"marisa",?"age":?16}, ????????{"name":?"koishi",?"age":?16}] data?=?[(d["age"],?d)?for?d?in?data] print(data) """ [(17,?{'name':?'satori',?'age':?17}),? ?(16,?{'name':?'marisa',?'age':?16}),? ?(16,?{'name':?'koishi',?'age':?16})] """ try: ????sorted_data?=?sorted(data) except?TypeError?as?e: ????print(e) """ '<'?not?supported?between?instances?of?'dict'?and?'dict' """
此時(shí)就報(bào)錯(cuò)了,因?yàn)榈诙€(gè)元組和第三個(gè)元組內(nèi)部的第一個(gè)元素都是 16,所以第一個(gè)元素相等,那么會(huì)比較第二個(gè)元素。而第二個(gè)元素是字典,字典之間無法比較,所以報(bào)錯(cuò)了。
但我們只是希望讓字典的 "age" 字段的值參與比較,如果相等的話,那么就不用再比較了,相對順序就保持現(xiàn)狀。所以我們可以這么做:
data?=?[{"name":?"satori",?"age":?17}, ????????{"name":?"marisa",?"age":?16}, ????????{"name":?"koishi",?"age":?16}] #?我們將索引也加進(jìn)去 data?=?[(d["age"],?i,?d)?for?i,?d?in?enumerate(data)] print(data) """ [(17,?0,?{'name':?'satori',?'age':?17}),? ?(16,?1,?{'name':?'marisa',?'age':?16}),? ?(16,?2,?{'name':?'koishi',?'age':?16})] """ #?如果?"age"?字段的值、或者說元組的第一個(gè)元素相等 #?那么就按照索引比較,索引一定是不重復(fù)的 sorted_data?=?sorted(data) print(sorted_data) """ [(16,?1,?{'name':?'marisa',?'age':?16}),? ?(16,?2,?{'name':?'koishi',?'age':?16}),? ?(17,?0,?{'name':?'satori',?'age':?17})] """ #?此時(shí)就成功排好序了 #?并且?"age"?字段的值相等的字典之間的相對順序 #?在排序之前和排序之后都保持一致 #?這正是我們想要的結(jié)果 sorted_data?=?[tpl[-1]?for?tpl?in?sorted_data] print(sorted_data) """ [{'name':?'marisa',?'age':?16},? ?{'name':?'koishi',?'age':?16},? ?{'name':?'satori',?'age':?17}] """
再比如,我們想要對元組排序,但我們希望按照元組的第二個(gè)元素進(jìn)行排序:
data?=?[("satori",?17), ????????("marisa",?15), ????????("koishi",?16)] data?=?[(tpl[1],?i,?tpl)?for?i,?tpl?in?enumerate(data)] print(data) """ [(17,?0,?('satori',?17)),? ?(15,?1,?('marisa',?15)),? ?(16,?2,?('koishi',?16))] """ sorted_data?=?sorted(data) print(sorted_data) """ [(15,?1,?('marisa',?15)),? ?(16,?2,?('koishi',?16)),? ?(17,?0,?('satori',?17))] """ sorted_data?=?[tpl[-1]?for?tpl?in?sorted_data] print(sorted_data) """ [('marisa',?15),? ?('koishi',?16),? ?('satori',?17)] """
所以當(dāng)可迭代對象內(nèi)部的元素?zé)o法進(jìn)行排序,或者說我們不希望基于整個(gè)元素進(jìn)行排序,那么就可以使用上面這個(gè)方法。將用來排序的值、索引、原始值放在一個(gè)元組里面,然后對元組排序,排完了再把最后一個(gè)值(也就是原始值)篩出來即可。
或者我們還可以做的再復(fù)雜一些:
data?=?[-3,?-2,?3,?2,?-1,?1,?0] """ 對?data?進(jìn)行排序,排序規(guī)則如下 先按照內(nèi)部元素的正負(fù)進(jìn)行排序,排序之后正數(shù)在后面 如果符號一樣,再按照絕對值的大小進(jìn)行排序 也就是說,排完之后是下面這樣一個(gè)結(jié)果 [-1,?-2,?-3,?0,?1,?2,?3] """ #?如果只按照正負(fù)排序 data1?=?[(n?>=?0,?i,?n)?for?i,?n?in?enumerate(data)] sorted_data?=?sorted(data1) print(sorted_data) """ [(False,?0,?-3),?(False,?1,?-2),?(False,?4,?-1),? ?(True,?2,?3),?(True,?3,?2),?(True,?5,?1),?(True,?6,?0)] """ sorted_data?=?[tpl[-1]?for?tpl?in?sorted_data] #?此時(shí)正數(shù)就排在了負(fù)數(shù)的后面 print( ????sorted_data )??#?[-3, -2, -1, 3, 2, 1, 0] #?如果只按照絕對值排序 data2?=?[(abs(n),?i,?n)?for?i,?n?in?enumerate(data)] sorted_data?=?sorted(data2) print(sorted_data) """ [(0,?6,?0),?(1,?4,?-1),?(1,?5,?1),? ?(2,?1,?-2),?(2,?3,?2),?(3,?0,?-3),?(3,?2,?3)] """ sorted_data?=?[tpl[-1]?for?tpl?in?sorted_data] #?此時(shí)絕對值大的數(shù),就排在了絕對值小的數(shù)的后面 print( ????sorted_data )??#?[0,?-1,?1,?-2,?2,?-3,?3] #?同時(shí)按照正負(fù)和絕對值排序 data3?=?[(n?>=?0,?abs(n),?i,?n)?for?i,?n?in?enumerate(data)] sorted_data?=?sorted(data3) print(sorted_data) """ [(False,?1,?4,?-1),?(False,?2,?1,?-2),? ?(False,?3,?0,?-3),?(True,?0,?6,?0),? ?(True,?1,?5,?1),?(True,?2,?3,?2),?(True,?3,?2,?3)] """ sorted_data?=?[tpl[-1]?for?tpl?in?sorted_data] #?大功告成 print( ????sorted_data )??#?[-1,?-2,?-3,?0,?1,?2,?3]
那么接下來,我們就可以封裝一個(gè)屬于我們自己的 my_sorted 函數(shù)了。
def?my_sorted(data,?*,?key=None,?reverse=False): ????""" ????:param?data:?可迭代對象 ????:param?key:?callable ????:param?reverse:?是否逆序 ????:return: ????""" ????if?key?is?not?None: ????????data?=?[(key(item),?i,?item) ????????????????for?i,?item?in?enumerate(data)] ????sorted_data?=?sorted(data) ????if?key?is?not?None: ????????sorted_data?=?[tpl[-1]?for?tpl?in?sorted_data] ????if?reverse: ????????sorted_data?=?sorted_data[::?-1] ????return?sorted_data #?下面來測試一下 data?=?[-3,?-2,?3,?2,?-1,?1,?0] print( ????my_sorted(data,?key=lambda?x:?(x?>=?0,?abs(x))) )??#?[-1,?-2,?-3,?0,?1,?2,?3] data?=?[{"name":?"satori",?"age":?17}, ????????{"name":?"marisa",?"age":?16}, ????????{"name":?"koishi",?"age":?16}] print(my_sorted(data,?key=lambda?x:?x["age"])) """ [{'name':?'marisa',?'age':?16},? ?{'name':?'koishi',?'age':?16},? ?{'name':?'satori',?'age':?17}] """
結(jié)果一切正常,當(dāng)然啦,實(shí)際工作中我們肯定不會(huì)專門封裝一個(gè) my_sorted 函數(shù),因?yàn)閮?nèi)置的 sorted 已經(jīng)包含了我們上面的所有功能。
data?=?[-3,?-2,?3,?2,?-1,?1,?0] print( ????sorted(data,?key=lambda?x:?(x?>=?0,?abs(x))) )??#?[-1,?-2,?-3,?0,?1,?2,?3] print( ????sorted(data,?key=lambda?x:?(x?>=?0,?abs(x)),?reverse=True) )??#?[3,?2,?1,?0,?-3,?-2,?-1]
內(nèi)置函數(shù) sorted 除了接收一個(gè)可迭代對象之外,還接收兩個(gè)關(guān)鍵字參數(shù) key 和 reverse,含義就是我們介紹的那樣。在 sorted 的內(nèi)部,它的處理方式和我們上面是一致的,如果指定了 key,也就是自定義排序規(guī)則,那么在底層會(huì)將可迭代對象內(nèi)部的值封裝成元組,然后對元組排序。排完序之后,再將元組的最后一個(gè)值、也就是原始值取出來,并返回。
所以這就是 sorted 函數(shù)的全部秘密,它里面的參數(shù) key 賦予了 sorted 函數(shù)強(qiáng)大的能力,有了這個(gè)參數(shù),我們想怎么排序,就怎么排序。
class?A: ????def?__init__(self,?a): ????????self.a?=?a ????def?__repr__(self): ????????return?f"self.a?=?{self.a}" ????def?__hash__(self): ????????return?self.a a1?=?A(1) a2?=?A(2) a3?=?A(3) a4?=?A(4) data?=?[a2,?a3,?a1,?a4] print(data) """ [self.a?=?2,?self.a?=?3,?self.a?=?1,?self.a?=?4] """ #?A?的實(shí)例對象無法比較,我們希望按照內(nèi)部的屬性?a?進(jìn)行比較 print(sorted(data,?key=lambda?x:?x.a)) """ [self.a?=?1,?self.a?=?2,?self.a?=?3,?self.a?=?4] """ #?或者按照哈希值比較,此時(shí)仍相當(dāng)于按照?self.a?比較 print(sorted(data,?key=lambda?x:?hash(x),?reverse=True)) """ [self.a?=?4,?self.a?=?3,?self.a?=?2,?self.a?=?1] """
因此我們想怎么比就怎么比,參數(shù) key 賦予了我們極大的自由,key 接收一個(gè)函數(shù)(當(dāng)然其它 callable 也可以,但大部分場景都是匿名函數(shù)),此函數(shù)接收一個(gè)參數(shù),該參數(shù)會(huì)對應(yīng)可迭代對象里面的每一個(gè)元素。而函數(shù)的返回值,決定了 sorted 的比較邏輯。
比如,我們不光可以對元組、列表排序,還可以對字典內(nèi)部的鍵值對排序。
d?=?{"satori":?17,?"marisa":?15,?"koishi":?16} #?對字典調(diào)用?sorted,針對的是字典里面的鍵 #?所以返回的也是鍵 print(sorted(d))??#?['koishi',?'marisa',?'satori'] #?匿名函數(shù)里面的參數(shù)?x?對應(yīng)可迭代對象里面的每一個(gè)元素 #?這里就是字典的鍵,函數(shù)返回?d[x]?表示按照值來排序 #?但排序之后得到的仍然是鍵 print( ????sorted(d,?key=lambda?x:?d[x]) )??#?['marisa',?'koishi',?'satori'] #?此時(shí)的?x?就是鍵值對組成的元組 #?這里按照值來排序 print( ????sorted(d.items(),?key=lambda?x:?x[1]) )??#?[('marisa',?15),?('koishi',?16),?('satori',?17)] #?如果排序的時(shí)候,還希望鍵值對調(diào)換順序,可以這么做 print( ????sorted(zip(d.values(),?d.keys()),?key=lambda?x:?x[0]) )??#?[(15,?'marisa'),?(16,?'koishi'),?(17,?'satori')]
當(dāng)然啦,還有很多其它排序方式,比如按照數(shù)量排序:
string?=?"a"?*?4?+?"b"?*?3?+?"c"?*?5?+?"d"?*?2 data?=?["a",?"b",?"c",?"d"] print( ????sorted(data,?key=lambda?x:?string.count(x)) )??#?['d',?'b',?'a',?'c']
最后再來介紹一個(gè)知識點(diǎn),sorted 在對可迭代對象內(nèi)部的元素進(jìn)行排序的時(shí)候,肯定要有大小比較的過程,這是肯定的。但問題是比較的時(shí)候,用的什么方式呢?舉個(gè)例子,我想判斷 a 和 b 的大小關(guān)系(假設(shè)不相等),無論是執(zhí)行 a > b 還是 a < b,根據(jù)結(jié)果我都能得出它們誰大誰小。
而 sorted 在比較的時(shí)候是怎么做的呢,這里給出結(jié)論:每次在比較兩個(gè)對象的時(shí)候,都會(huì)調(diào)用左邊對象的 __lt__ 方法。其實(shí)關(guān)于 sorted 內(nèi)部是怎么比的,我們無需太關(guān)注,但之所以說這一點(diǎn),是因?yàn)樵跇O端場景下可能會(huì)遇到。舉個(gè)例子:
#?第一個(gè)元素表示?"商品名稱" #?第二個(gè)元素表示?"銷量" data?=?[("apple",?200), ????????("banana",?200), ????????("peach",?150), ????????("cherry",?150), ????????("orange",?150)] #?我們需要先按照?"銷量"?的大小降序排序 #?如果?"銷量"?相同,則按照?"商品名稱"?的字典序升序排序 #?該怎么做呢? #?由于一部分升序,一部分降序 #?我們無法直接使用?reverse?參數(shù),所以就默認(rèn)按照升序排 #?雖然 "銷量" 要求降序排,但可以對它取反 #?這樣值越大,取反之后的值就越小,從而實(shí)現(xiàn)降序效果 print( ????sorted(data,?key=lambda?x:?(~x[1],?x[0])) ) """ [('apple',?200),? ?('banana',?200),? ?('cherry',?150),? ?('orange',?150),? ?('peach',?150)] """
可能有小伙伴覺得這也沒什么難的,那么我們將問題稍微換一下。如果讓你先按照 "銷量" 升序排序,如果 "銷量相同",再按照 "商品名稱" 的字典序降序排序,你要怎么做呢?
顯然這個(gè)問題的難點(diǎn)就在于字符串要怎么降序排,整數(shù)可以取反,但字符串是無法取反的。所以我們可以自定義一個(gè)類,實(shí)現(xiàn)它的 __lt__ 方法。
data?=?[("apple",?200), ????????("banana",?200), ????????("peach",?150), ????????("cherry",?150), ????????("orange",?150)] class?STR(str): ????def?__lt__(self,?other): ????????#?調(diào)用?str?的?__lt__,得到布爾值 ????????#?然后再取反,當(dāng)然,把?not?換成?~?也是可以的 ????????#?因此:"apple"?<?"banana"?為 True ????????#?但是:STR("apple")?< STR("banana")?為 False ????????return?not?super().__lt__(other) #?銷量升序排,直接?x[1]?即可 #?但是商品名稱降序排,需要使用類?STR?將?x[0]?包起來 print( ????sorted(data,?key=lambda?x:?(x[1],?STR(x[0]))) ) """ [('peach',?150),? ?('orange',?150),? ?('cherry',?150),? ?('banana',?200),? ?('apple',?200)] """ #?事實(shí)上,如果你的思維夠靈活,你會(huì)發(fā)現(xiàn) #?"銷量"降序排、"商品名稱"升序排,排完之后再整體取反 #?就是這里?"銷量"升序排、"商品名稱"將序排?的結(jié)果 print( ????sorted(data,?key=lambda?x:?(~x[1],?x[0]),?reverse=True) ????== ????sorted(data,?key=lambda?x:?(x[1],?STR(x[0]))) )??#?True #?當(dāng)然這個(gè)思路也很巧妙
由于默認(rèn)是調(diào)用 __lt__ 進(jìn)行比較的,因此我們需要實(shí)現(xiàn) __lt__。
以上就是一文帶你解密Python可迭代對象的排序問題的詳細(xì)內(nèi)容,更多關(guān)于Python可迭代對象排序的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談tensorflow中Dataset圖片的批量讀取及維度的操作詳解
今天小編就為大家分享一篇淺談tensorflow中Dataset圖片的批量讀取及維度的操作詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01pandas實(shí)現(xiàn)按照多列排序-ascending
這篇文章主要介紹了pandas實(shí)現(xiàn)按照多列排序-ascending,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05快速排序的四種python實(shí)現(xiàn)(推薦)
這篇文章主要介紹了python實(shí)現(xiàn)快速排序算法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Python實(shí)現(xiàn)12種降維算法的示例代碼
數(shù)據(jù)降維算法是機(jī)器學(xué)習(xí)算法中的大家族,與分類、回歸、聚類等算法不同,它的目標(biāo)是將向量投影到低維空間,以達(dá)到某種目的如可視化,或是做分類。本文將利用Python實(shí)現(xiàn)12種降維算法,需要的可以參考一下2022-04-04python之Character string(實(shí)例講解)
下面小編就為大家?guī)硪黄猵ython之Character string(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09python中的reduce內(nèi)建函數(shù)使用方法指南
python中的reduce內(nèi)建函數(shù)是一個(gè)二元操作函數(shù),他用來將一個(gè)數(shù)據(jù)集合(鏈表,元組等)中的所有數(shù)據(jù)進(jìn)行下列操作:用傳給reduce中的函數(shù) func()(必須是一個(gè)二元操作函數(shù))先對集合中的第1,2個(gè)數(shù)據(jù)進(jìn)行操作,得到的結(jié)果再與第三個(gè)數(shù)據(jù)用func()函數(shù)運(yùn)算,最后得到一個(gè)結(jié)果2014-08-08Python的Flask框架應(yīng)用程序?qū)崿F(xiàn)使用QQ賬號登錄的方法
利用QQ開放平臺(tái)的API使用QQ賬號登錄是現(xiàn)在很多網(wǎng)站都具備的功能,而對于Flask框架來說則有Flask-OAuthlib這個(gè)現(xiàn)成的輪子,這里我們就來看一下Python的Flask框架應(yīng)用程序?qū)崿F(xiàn)使用QQ賬號登錄的方法2016-06-06詳解mac python+selenium+Chrome 簡單案例
這篇文章主要介紹了詳解mac python+selenium+Chrome 簡單案例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11