<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
假設有一個可迭代物件,現在想要對它內部的元素進行排序,我們一般會使用內建函數 sorted,舉個例子:
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']
如果可迭代物件裡面的元素是數值,那麼會按照數值的大小進行排序;如果是字串,那麼會按照字串的字典序進行排序,並且 sorted 函數會返回一個新的列表。
sorted 函數預設是升序排序,如果想要降序,那麼可以傳遞一個關鍵字引數 reverse=True。
data = [(3, 4), (3, 1), (2, 3)] print( sorted(data) ) # [(2, 3), (3, 1), (3, 4)]
如果可迭代物件裡面都是元組的話,也是可以的,元組在比較大小的時候,會先按照元組的第一個元素比較;第一個元素相等,再按照第二個元素比較,依次類推。
因此在使用 sorted 函數的時候,可迭代物件內部的元素,要滿足彼此之間都是可比較的,否則報錯。
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 裡面存在不可比較的元素,因此報錯了。那麼問題來了,假設有這樣一個列表:
data = [{"name": "satori", "age": 17}, {"name": "marisa", "age": 15}, {"name": "koishi", "age": 16}] # 字典是不可比較大小的,因此直接使用 sorted 會報錯 # 我們希望按照字典內部的 "age" 欄位進行排序 # 得到下面的結果 [{'name': 'marisa', 'age': 15}, {'name': 'koishi', 'age': 16}, {'name': 'satori', 'age': 17}]
如果是你的話,你會怎麼做呢?很明顯,我們將每個 "age" 欄位的值取出來,和所在的字典拼接成一個元組(或列表)不就行了,然後對元組進行排序,舉個例子:
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 內部的元素是一個元組 # 所以排序的時候會按照元組的第一個元素排序 sorted_data = sorted(data) print(sorted_data) """ [(15, {'name': 'marisa', 'age': 15}), (16, {'name': 'koishi', 'age': 16}), (17, {'name': 'satori', 'age': 17})] """ # 此時順序就排好了,然後再把字典取出來 sorted_data = [tpl[-1] for tpl in sorted_data] print(sorted_data) """ [{'name': 'marisa', 'age': 15}, {'name': 'koishi', 'age': 16}, {'name': 'satori', 'age': 17}] """
顯然這樣就實現了基於字典內部某個欄位的值,來對字典進行排序,只不過上面的程式碼還有一點點缺陷。我們說元組在比較的時候會先比較第一個元素,第一個元素相同的話,會比較第二個元素。
而我們上面 data 裡面的元組,由於第一個元素都不相等,所以直接就比較出來了。但如果是下面這種情況呢?
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' """
此時就報錯了,因為第二個元組和第三個元組內部的第一個元素都是 16,所以第一個元素相等,那麼會比較第二個元素。而第二個元素是字典,字典之間無法比較,所以報錯了。
但我們只是希望讓字典的 "age" 欄位的值參與比較,如果相等的話,那麼就不用再比較了,相對順序就保持現狀。所以我們可以這麼做:
data = [{"name": "satori", "age": 17}, {"name": "marisa", "age": 16}, {"name": "koishi", "age": 16}] # 我們將索引也加進去 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" 欄位的值、或者說元組的第一個元素相等 # 那麼就按照索引比較,索引一定是不重複的 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})] """ # 此時就成功排好序了 # 並且 "age" 欄位的值相等的字典之間的相對順序 # 在排序之前和排序之後都保持一致 # 這正是我們想要的結果 sorted_data = [tpl[-1] for tpl in sorted_data] print(sorted_data) """ [{'name': 'marisa', 'age': 16}, {'name': 'koishi', 'age': 16}, {'name': 'satori', 'age': 17}] """
再比如,我們想要對元組排序,但我們希望按照元組的第二個元素進行排序:
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)] """
所以當可迭代物件內部的元素無法進行排序,或者說我們不希望基於整個元素進行排序,那麼就可以使用上面這個方法。將用來排序的值、索引、原始值放在一個元組裡面,然後對元組排序,排完了再把最後一個值(也就是原始值)篩出來即可。
或者我們還可以做的再複雜一些:
data = [-3, -2, 3, 2, -1, 1, 0] """ 對 data 進行排序,排序規則如下 先按照內部元素的正負進行排序,排序之後正數在後面 如果符號一樣,再按照絕對值的大小進行排序 也就是說,排完之後是下面這樣一個結果 [-1, -2, -3, 0, 1, 2, 3] """ # 如果只按照正負排序 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] # 此時正數就排在了負數的後面 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] # 此時絕對值大的數,就排在了絕對值小的數的後面 print( sorted_data ) # [0, -1, 1, -2, 2, -3, 3] # 同時按照正負和絕對值排序 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]
那麼接下來,我們就可以封裝一個屬於我們自己的 my_sorted 函數了。
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}] """
結果一切正常,當然啦,實際工作中我們肯定不會專門封裝一個 my_sorted 函數,因為內建的 sorted 已經包含了我們上面的所有功能。
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]
內建函數 sorted 除了接收一個可迭代物件之外,還接收兩個關鍵字引數 key 和 reverse,含義就是我們介紹的那樣。在 sorted 的內部,它的處理方式和我們上面是一致的,如果指定了 key,也就是自定義排序規則,那麼在底層會將可迭代物件內部的值封裝成元組,然後對元組排序。排完序之後,再將元組的最後一個值、也就是原始值取出來,並返回。
所以這就是 sorted 函數的全部祕密,它裡面的引數 key 賦予了 sorted 函數強大的能力,有了這個引數,我們想怎麼排序,就怎麼排序。
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 的範例物件無法比較,我們希望按照內部的屬性 a 進行比較 print(sorted(data, key=lambda x: x.a)) """ [self.a = 1, self.a = 2, self.a = 3, self.a = 4] """ # 或者按照雜湊值比較,此時仍相當於按照 self.a 比較 print(sorted(data, key=lambda x: hash(x), reverse=True)) """ [self.a = 4, self.a = 3, self.a = 2, self.a = 1] """
因此我們想怎麼比就怎麼比,引數 key 賦予了我們極大的自由,key 接收一個函數(當然其它 callable 也可以,但大部分場景都是匿名函數),此函數接收一個引數,該引數會對應可迭代物件裡面的每一個元素。而函數的返回值,決定了 sorted 的比較邏輯。
比如,我們不光可以對元組、列表排序,還可以對字典內部的鍵值對排序。
d = {"satori": 17, "marisa": 15, "koishi": 16} # 對字典呼叫 sorted,針對的是字典裡面的鍵 # 所以返回的也是鍵 print(sorted(d)) # ['koishi', 'marisa', 'satori'] # 匿名函數裡面的引數 x 對應可迭代物件裡面的每一個元素 # 這裡就是字典的鍵,函數返回 d[x] 表示按照值來排序 # 但排序之後得到的仍然是鍵 print( sorted(d, key=lambda x: d[x]) ) # ['marisa', 'koishi', 'satori'] # 此時的 x 就是鍵值對組成的元組 # 這裡按照值來排序 print( sorted(d.items(), key=lambda x: x[1]) ) # [('marisa', 15), ('koishi', 16), ('satori', 17)] # 如果排序的時候,還希望鍵值對調換順序,可以這麼做 print( sorted(zip(d.values(), d.keys()), key=lambda x: x[0]) ) # [(15, 'marisa'), (16, 'koishi'), (17, 'satori')]
當然啦,還有很多其它排序方式,比如按照數量排序:
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']
最後再來介紹一個知識點,sorted 在對可迭代物件內部的元素進行排序的時候,肯定要有大小比較的過程,這是肯定的。但問題是比較的時候,用的什麼方式呢?舉個例子,我想判斷 a 和 b 的大小關係(假設不相等),無論是執行 a > b 還是 a < b,根據結果我都能得出它們誰大誰小。
而 sorted 在比較的時候是怎麼做的呢,這裡給出結論:每次在比較兩個物件的時候,都會呼叫左邊物件的 __lt__ 方法。其實關於 sorted 內部是怎麼比的,我們無需太關注,但之所以說這一點,是因為在極端場景下可能會遇到。舉個例子:
# 第一個元素表示 "商品名稱" # 第二個元素表示 "銷量" data = [("apple", 200), ("banana", 200), ("peach", 150), ("cherry", 150), ("orange", 150)] # 我們需要先按照 "銷量" 的大小降序排序 # 如果 "銷量" 相同,則按照 "商品名稱" 的字典序升序排序 # 該怎麼做呢? # 由於一部分升序,一部分降序 # 我們無法直接使用 reverse 引數,所以就預設按照升序排 # 雖然 "銷量" 要求降序排,但可以對它取反 # 這樣值越大,取反之後的值就越小,從而實現降序效果 print( sorted(data, key=lambda x: (~x[1], x[0])) ) """ [('apple', 200), ('banana', 200), ('cherry', 150), ('orange', 150), ('peach', 150)] """
可能有小夥伴覺得這也沒什麼難的,那麼我們將問題稍微換一下。如果讓你先按照 "銷量" 升序排序,如果 "銷量相同",再按照 "商品名稱" 的字典序降序排序,你要怎麼做呢?
顯然這個問題的難點就在於字串要怎麼降序排,整數可以取反,但字串是無法取反的。所以我們可以自定義一個類,實現它的 __lt__ 方法。
data = [("apple", 200), ("banana", 200), ("peach", 150), ("cherry", 150), ("orange", 150)] class STR(str): def __lt__(self, other): # 呼叫 str 的 __lt__,得到布林值 # 然後再取反,當然,把 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)] """ # 事實上,如果你的思維夠靈活,你會發現 # "銷量"降序排、"商品名稱"升序排,排完之後再整體取反 # 就是這裡 "銷量"升序排、"商品名稱"將序排 的結果 print( sorted(data, key=lambda x: (~x[1], x[0]), reverse=True) == sorted(data, key=lambda x: (x[1], STR(x[0]))) ) # True # 當然這個思路也很巧妙
由於預設是呼叫 __lt__ 進行比較的,因此我們需要實現 __lt__。
以上就是一文帶你解密Python可迭代物件的排序問題的詳細內容,更多關於Python可迭代物件排序的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45