近日瀏覽LeetCode,發(fā)現(xiàn)了一道很有意思的小題目,。當(dāng)我嘗試用Python解答的時候,,居然動用了集合、map函數(shù),、zip函數(shù),、lambda函數(shù)、sorted函數(shù),調(diào)試過程還涉及到了迭代器,、生成器,、列表推導(dǎo)式的概念。 一個看似極為簡單的題目,,盡管最終的代碼可以合并成一行,,卻幾乎把Python的編程技巧用了一遍,真可謂“細(xì)微之處見精神”,!通過這個題目,,也許會讓你從此真正理解了Python編程。 這道題,,名為《列表中的幸運數(shù)》,。什么是幸運數(shù)呢?在整數(shù)列表中,,如果一個數(shù)字的出現(xiàn)頻次和它的數(shù)值大小相等,,我們就稱這個數(shù)字為「幸運數(shù)」。 例如,,在列表[1, 2, 2, 3]中,,數(shù)字1和數(shù)字2出現(xiàn)的次數(shù)分別是1和2,所以它們是幸運數(shù),,但3只出現(xiàn)過1次,,3不是幸運數(shù)。明白了幸運數(shù)的概念,,我們就來試著找出列表[3, 5, 2, 7, 3, 1, 2 ,4, 8, 9, 3]中的幸運數(shù)吧,。 這個過程可以分為以下幾個步驟: 統(tǒng)計每個數(shù)字在列表中出現(xiàn)的次數(shù) 找出出現(xiàn)次數(shù)等于數(shù)字本身的那些數(shù)字
第1步,,找出列表中不重復(fù)的數(shù)字找出列表中不重復(fù)的數(shù)字,,也就是去除列表中的重復(fù)元素,簡稱“去重”,。去重最簡潔的方法是使用集合,。 >>> arr = [3,5,2,7,3,8,1,2,4,8,9,3] >>> unique = set(arr) >>> unique {1, 2, 3, 4, 5, 7, 8, 9}
第2步,統(tǒng)計每個數(shù)字在列表中出現(xiàn)的次數(shù)我們知道,,列表對象自帶一個count()方法,,能返回某個元素在列表中出現(xiàn)的次數(shù),具體用法如下: >>> arr = [3,5,2,7,3,8,1,2,4,8,9,3] >>> arr.count(8) # 元素8在數(shù)組arr中出現(xiàn)過2次 2
接下來,,我們只需要遍歷去重后的各個元素,,逐一統(tǒng)計它們各自出現(xiàn)的次數(shù),并保存成一個合適的數(shù)據(jù)結(jié)構(gòu),,這一步工作就萬事大吉了,。 >>> arr = [3,5,2,7,3,8,1,2,4,8,9,3] >>> unique = set(arr) # 去除重復(fù)元素 >>> pairs = list() # 空列表,用于保存數(shù)組元素和出現(xiàn)次數(shù)組成的元組 >>> for i in unique: pairs.append((i, arr.count(i))) >>> pairs [(1, 1), (2, 2), (3, 3), (4, 1), (5, 1), (7, 1), (8, 2), (9, 1)]
作為新手,代碼寫成這樣,,已經(jīng)很不錯了,。但是,一個有追求的程序員絕對不會就此自滿,、裹足不前,。他們最喜歡做的事情就是想盡千方百計消滅for循環(huán),比如使用映射函數(shù),、過濾函數(shù)取代for循環(huán),;即便不能拒絕for循環(huán),他們也會盡可能把循環(huán)藏起來,,比如藏在列表推導(dǎo)式內(nèi),。這里既然是要對每一個元素都調(diào)用列表的count()這個方法,那就最適合用map函數(shù)取代for循環(huán)了,。 >>> m = map(arr.count, unique) >>> m <map object at 0x0000020A2D090E08> >>> list(m) # 生成器可以轉(zhuǎn)成列表 [1, 2, 3, 1, 1, 1, 2, 1] >>> list(m) # 生成器只能用一次,,用過之后,就自動清理了 []
map函數(shù)返回的是一個生成器(generator),,可以像列表一樣遍歷,,但無法像列表那樣直觀地看到各個元素,除非我們用list()把這個生成器轉(zhuǎn)成列表(實際上并不需要將生成器轉(zhuǎn)為列表),。請注意,,生成器和迭代器不同,或者說生成器是一種特殊的迭代器,,只能被遍歷一次,,遍歷結(jié)束,就自動消失了,。迭代器則可以反復(fù)遍歷,。比如,range()函數(shù)返回的就是迭代器: >>> a = range(5) >>> list(a) [0, 1, 2, 3, 4] >>> list(a) [0, 1, 2, 3, 4]
說完生成器和迭代器,,咱們還得回到原來的話題上,。使用map映射函數(shù),我們得到了每個元素的出現(xiàn)次數(shù),,還需要和對應(yīng)的元素組成一個一個的元組,。這時候,就用上zip()函數(shù)了,。zip() 函數(shù)創(chuàng)建一個生成器,,用來聚合每個可迭代對象(迭代器、生成器,、列表,、元組,、集合、字符串等)的元素,,元素按照相同下標(biāo)聚合,,長度不同則忽略大于最短迭代對象長度的元素。 >>> m = map(arr.count, unique) >>> z = zip(unique, m) >>> z <zip object at 0x0000020A2D490508> >>> list(z) [(1, 1), (2, 2), (3, 3), (4, 1), (5, 1), (7, 1), (8, 2), (9, 1)] >>> list(z) []
很顯然,,zip()函數(shù)返回的也是生成器,,只能用一次,過后即消失,。 第3步,,找出出現(xiàn)次數(shù)等于數(shù)字本身的那些數(shù)字有了每個元素及其出現(xiàn)的次數(shù),我們只需要循環(huán)遍歷……不,,請稍等,,我們?yōu)槭裁匆欢ㄒh(huán)呢?我們只是要把每個元素過濾一遍,,找出那些出現(xiàn)次數(shù)等于元素自身的那些元組,,為什么不試試過濾函數(shù)filter()呢? >>> def func(x): # 參數(shù)x是元組類型 if x[0] == x[1]: return x >>> m = map(arr.count, unique) >>> z = zip(unique, m) >>> f = filter(func, z) >>> f <filter object at 0x0000020A2D1DD908> >>> list(f) [(1, 1), (2, 2), (3, 3)] >>> list(f) []
過濾函數(shù)filter()接受兩個參數(shù),,第1個參數(shù)是個函數(shù),,用于判斷一個元素是否符合過濾條件,第2個參數(shù)就是需要過濾的可迭代對象了,。filter()函數(shù)返回的也是生成器,,只能用一次,過后即消失,。寫這里,,我們幾乎要大功告成了。但是,,作為一個有追求的程序員,,你能容忍func()這樣一個看起來怪怪的函數(shù)嗎?答案是不能,!你一定會用lambda函數(shù)取代它,。另外,也許我們還需要對結(jié)果按照元素的大小排序,。加上排序,完整代碼如下: >>> arr = [3,5,2,7,3,8,1,2,4,8,9,3] >>> unique = set(arr) >>> m = map(arr.count, unique) >>> z = zip(unique, m) >>> f = filter(lambda x:x[0]==x[1], z) >>> s = sorted(f, key=lambda x:x[0]) >>> print('幸運數(shù)是:', [item[0] for item in s]) 幸運數(shù)是: [1, 2, 3]
終極代碼,,一行搞定如果你曾經(jīng)有過被那些寫成一行,、卻能實現(xiàn)復(fù)雜功能的、看起來像天書一樣的代碼蹂躪的痛苦經(jīng)歷,,那么,,現(xiàn)在你也可以把上面的代碼寫成一行,,去蹂躪別人了。 >>> arr = [3,5,2,7,3,8,1,2,4,8,9,3] >>> print('幸運數(shù)是:', [item[0] for item in sorted(filter(lambda x:x[0]==x[1], zip(set(arr), map(arr.count, set(arr)))), key=lambda x:x[0])]) 幸運數(shù)是: [1, 2, 3]
戲劇性反轉(zhuǎn),,這次真的理解Python了,!有人說,何必那么麻煩呢,?這樣寫不是更簡單,、更易讀嗎?果然,,我真是想多了,! >>> arr = [3,5,2,7,3,8,1,2,4,8,9,3] >>> [x for x in set(arr) if x == arr.count(x)] [1, 2, 3]
|