Python3 CookBook | 數據結構和演算法(二)

以下測試代碼全部基於 Python3

1、查找最大或最小的 N 個元素

工作中有時會遇到這樣的需求,取出數據中前面 10% 的值,或者最後 10% 的值。

我們可以先對這個列表進行排序,然後再進行切片操作,很輕鬆的解決這個問題。但是,有沒有更好的方法呢?

heapq 模塊有兩個函數 nlargest() 和 nsmallest() 可以完美解決這個問題。

In [50]: import heapqnnIn [51]: n = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2, 23, 45, 76]nnIn [52]: heapq.nlargest(3, n)nOut[52]: [76, 45, 42]nnIn [53]: heapq.nsmallest(3, n)nOut[53]: [-4, 1, 2]n

如果是取排在前面的 10% 應該怎麼做?

heapq.nlargest(round(len(n)/10), n)n

而且,使用這兩個函數還會有更好的性能,因為在底層實現裡面,會先把數據進行堆排序後放入一個列表中,然後再進行後續操作。大家如果對堆數據結構感興趣的話,可以繼續進行深入研究,由於我了解的並不深,也沒辦法再展開了。

但是也並不是什麼時候都是這兩個函數效果更好,比如只取一個最大值或者最小值,那還是 min() 或 max() 效果更好;如果要查找的元素個數已經跟集合元素個數接近時,那還是用 sorted(items)[:N] 更好,具體情況具體分析吧。

2、序列中出現次數最多的元素

以前碰到這類問題時,我都會手動創建一個字典,然後以列表中元素作為 key,進而統計出 key 出現的次數,再進行比較得到出現次數最多的元素。

殊不知 collections 中就有專門為這類問題設計的類 Counter,瞬間感覺自己蠢爆了,話不多說,直接上代碼。

In [54]: from collections import CounternnIn [55]: w = [a, b, c, d, a, a, b]nnIn [56]: w_count = Counter(w)nnIn [57]: w_countnOut[57]: Counter({a: 3, b: 2, c: 1, d: 1})nnIn [58]: w_count[a]nOut[58]: 3nnIn [59]: top = w_count.most_common(2)nnIn [60]: topnOut[60]: [(a, 3), (b, 2)]n

可以看到,Counter 返回的就是一個字典,想知道哪個元素出現幾次,直接取,是不是很方便?

而且還有 most_common 函數,簡直不要太棒。

3、過濾序列元素

有一個列表,如下:

In [61]: a = [1, 2, 3, 4, 5, -3]n

要求過濾所有負數。需要新建一個列表?直接一行代碼搞定。

In [64]: [n for n in a if n > 0]nOut[64]: [1, 2, 3, 4, 5]n

如果要把負數替換成 0 呢?

In [67]: [n if n > 0 else 0 for n in a]nOut[67]: [1, 2, 3, 4, 5, 0]n

但是有時候過濾條件可能比較複雜,這時就需要藉助於 filter() 函數了。

values = [1, 2, -3, -, 4, N/A, 5]ndef is_int(val):n try:n x = int(val)n return Truen except ValueError:n return Falsennivals = list(filter(is_int, values))nprint(ivals)n# Outputs [1, 2, -3, 4, 5]n

4、通過某個關鍵字將記錄分組

有下面這個字典:

rows = [n {address: 5412 N CLARK, date: 07/01/2012},n {address: 5148 N CLARK, date: 07/04/2012},n {address: 5800 E 58TH, date: 07/02/2012},n {address: 2122 N CLARK, date: 07/03/2012},n {address: 5645 N RAVENSWOOD, date: 07/02/2012},n {address: 1060 W ADDISON, date: 07/02/2012},n {address: 4801 N BROADWAY, date: 07/01/2012},n {address: 1039 W GRANVILLE, date: 07/04/2012},n]n

那麼怎麼對這個字典按照 date 進行分組呢?藉助於 itertools.groupby() 函數可以解決這個問題,代碼如下:

# Sort by the desired field firstnrows.sort(key=itemgetter(date))n# Iterate in groupsnfor date, items in groupby(rows, key=itemgetter(date)):n print(date)n for i in items:n print( , i)n

輸出結果如下:

07/01/2012n {address: 5412 N CLARK, date: 07/01/2012}n {address: 4801 N BROADWAY, date: 07/01/2012}n07/02/2012n {address: 5800 E 58TH, date: 07/02/2012}n {address: 5645 N RAVENSWOOD, date: 07/02/2012}n {address: 1060 W ADDISON, date: 07/02/2012}n07/03/2012n {address: 2122 N CLARK, date: 07/03/2012}n07/04/2012n {address: 5148 N CLARK, date: 07/04/2012}n {address: 1039 W GRANVILLE, date: 07/04/2012}n

需要注意的是,groupby() 函數僅僅檢查連續相同的元素,所以在分組之前,一定要先對數據,按照分組欄位進行排序。如果沒有排序,便得不到想要的結果。

5、映射名稱到序列元素

我常常有這樣的苦惱,就是有一個列表,然後通過下標來取值,取值時很認真的數所需要元素在第幾個,很怕取錯值。取到值後開始下面的運算。

一段時間之後,再看這段代碼,感覺很陌生,已經忘了帶下標的值是什麼了,還需要重新看一下這個列表的由來,才找到回憶。

如果能有一個名稱映射到元素上就好了,直接通過名稱就可以知道元素的含義。collections.namedtuple() 函數就可以解決這個問題。

In [76]: from collections import namedtuplennIn [77]: subscriber = namedtuple(Subscriber, [addr, joined])nnIn [78]: sub = subscriber(jonesy@example.com, 2012-10-19)nnIn [79]: subnOut[79]: Subscriber(addr=jonesy@example.com, joined=2012-10-19)nnIn [80]: sub.addrnOut[80]: jonesy@example.comnnIn [81]: sub.joinednOut[81]: 2012-10-19n

這樣就可以通過名稱來取值了,代碼可讀性也更高。

需要注意的是,這種命名元祖的方式不能直接修改其中的值,直接修改會報錯

In [82]: a = namedtuple(SSS, [name, shares, price])nnIn [83]: _a = a(yongxinz, 1, 2)nnIn [84]: _a.shares = 4n-----------------------------------------------------------------------nAttributeError Traceback (most recent call last)n<ipython-input-84-f62a5288a29a> in <module>()n> 1 _a.shares = 4nnAttributeError: cant set attributen

想要修改的話可以使用 _replace() 函數。

In [85]: _a._replace(shares=4)nOut[85]: SSS(name=yongxinz, shares=4, price=2)n

但是還有一個疑問,如果這個列表元素比較多的話,那就需要定義很多的名稱,也比較麻煩,還有更好的方式嗎?

未完待續。。。

歡迎留言,或添加我個人微信 zhangyx6a 交流通過,不是微商。


推薦閱讀:

scanf_s 比起 scanf 添加了什麼?
關於漢字、亂碼和其他
人機對戰初體驗:Python實現四子棋遊戲
GitHub排第一的語言為什麼是js?
學習編程感覺吃力怎麼辦?

TAG:Python | 编程 | 算法与数据结构 |