有沒有那麼一道題,讓你從此真正理解了Python編程?

寫下這個題目的時候,腦海裏無法抑制地響起了周華健那略帶沙啞的歌聲:


遠處傳來那首熟悉的歌,

那些心聲爲何那樣微弱。

很久不見,你現在都還好嗎?

有沒有那麼一首歌,

會讓你輕輕跟着和,

隨着我們生命起伏,

一起唱的主題歌;

有沒有那麼一首歌,

會讓你突然想起我,

讓你歡喜也讓你憂,

這麼一個我……

音樂結束,回到正題。近日瀏覽LeetCode,發現了一道很有意思的小題目。當我嘗試用Python解答的時候,居然動用了集合、map函數、zip函數、lambda函數、sorted函數,調試過程還涉及到了迭代器、生成器、列表推導式的概念。一個看似極爲簡單的題目,儘管最終的代碼可以合併成一行,卻幾乎把Python的編程技巧用了一遍,真可謂“細微之處見精神”!通過這個題目,也許會讓你從此真正理解了Python編程。


這道題,名爲《列表中的幸運數》。什麼是幸運數呢?在整數列表中,如果一個數字的出現頻次和它的數值大小相等,我們就稱這個數字爲「幸運數」。例如,在列表[1, 2, 2, 3]中,數字1和數字2出現的次數分別是1和2,所以它們是幸運數,但3只出現過1次,3不是幸運數。


明白了幸運數的概念,我們就來試着找出列表[3, 5, 2, 7, 3, 1, 2 ,4, 8, 9, 3]中的幸運數吧。這個過程可以分爲以下幾個步驟:

http://點擊鏈接加入羣聊【Python技術交流1】:https://jq.qq.com/?_wv=1027&k=hNUdOpGo

找出列表中不重複的數字

統計每個數字在列表中出現的次數

找出出現次數等於數字本身的那些數字

第1步,找出列表中不重複的數字

找出列表中不重複的數字,也就是去除列表中的重複元素,簡稱“去重”。去重最簡潔的方法是使用集合。

>>> 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步,統計每個數字在列表中出現的次數

我們知道,列表對象自帶一個count()方法,能返回某個元素在列表中出現的次數,具體用法如下:


>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]

>>> arr.count(8) # 元素8在數組arr中出現過2次


接下來,我們只需要遍歷去重後的各個元素,逐一統計它們各自出現的次數,並保存成一個合適的數據結構,這一步工作就萬事大吉了。


>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]

>>> unique = set(arr) # 去除重複元素

>>> pairs = list() # 空列表,用於保存數組元素和出現次數組成的元組

>>> 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)]


作爲新手,代碼寫成這樣,已經很不錯了。但是,一個有追求的程序員絕對不會就此自滿、裹足不前。他們最喜歡做的事情就是想盡千方百計消滅for循環,比如使用映射函數、過濾函數取代for循環;即便不能拒絕for循環,他們也會儘可能把循環藏起來,比如藏在列表推導式內。這裏既然是要對每一個元素都調用列表的count()這個方法,那就最適合用map函數取代for循環了。


>>> m = map(arr.count, unique)

>>> m

<map object at 0x0000020A2D090E08>

>>> list(m) # 生成器可以轉成列表

[1, 2, 3, 1, 1, 1, 2, 1]

>>> list(m) # 生成器只能用一次,用過之後,就自動清理了

[]


map函數返回的是一個生成器(generator),可以像列表一樣遍歷,但無法像列表那樣直觀地看到各個元素,除非我們用list()把這個生成器轉成列表(實際上並不需要將生成器轉爲列表)。請注意,生成器和迭代器不同,或者說生成器是一種特殊的迭代器,只能被遍歷一次,遍歷結束,就自動消失了。迭代器則可以反覆遍歷。比如,range()函數返回的就是迭代器:


>>> a = range(5)

>>> list(a)

[0, 1, 2, 3, 4]

>>> list(a)

[0, 1, 2, 3, 4]


說完生成器和迭代器,咱們還得回到原來的話題上。使用map映射函數,我們得到了每個元素的出現次數,還需要和對應的元素組成一個一個的元組。這時候,就用上zip()函數了。zip() 函數創建一個生成器,用來聚合每個可迭代對象(迭代器、生成器、列表、元組、集合、字符串等)的元素,元素按照相同下標聚合,長度不同則忽略大於最短迭代對象長度的元素。


>>> 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()函數返回的也是生成器,只能用一次,過後即消失。


第3步,找出出現次數等於數字本身的那些數字

有了每個元素及其出現的次數,我們只需要循環遍歷……不,請稍等,我們爲什麼一定要循環呢?我們只是要把每個元素過濾一遍,找出那些出現次數等於元素自身的那些元組,爲什麼不試試過濾函數filter()呢?


>>> def func(x): # 參數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)

[]


過濾函數filter()接受兩個參數,第1個參數是個函數,用於判斷一個元素是否符合過濾條件,第2個參數就是需要過濾的可迭代對象了。filter()函數返回的也是生成器,只能用一次,過後即消失。


寫這裏,我們幾乎要大功告成了。但是,作爲一個有追求的程序員,你能容忍func()這樣一個看起來怪怪的函數嗎?答案是不能!你一定會用lambda函數取代它。另外,也許我們還需要對結果按照元素的大小排序。加上排序,完整代碼如下:


>>> 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('幸運數是:', [item[0] for item in s])

幸運數是: [1, 2, 3]


終極代碼,一行搞定

如果你曾經有過被那些寫成一行、卻能實現複雜功能的、看起來像天書一樣的代碼蹂躪的痛苦經歷,那麼,現在你也可以把上面的代碼寫成一行,去蹂躪別人了。


>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]

>>> print('幸運數是:', [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])])

幸運數是: [1, 2, 3]


戲劇性反轉,這次真的理解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]

————————————————

原文鏈接:https://blog.csdn.net/xufive/article/details/105215593


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章