文章首發於知乎專欄,歡迎關注。
https://zhuanlan.zhihu.com/py...python
如下測試代碼所有基於 Python3微信
工做中有時會遇到這樣的需求,取出數據中前面 10% 的值,或者最後 10% 的值。數據結構
咱們能夠先對這個列表進行排序,而後再進行切片操做,很輕鬆的解決這個問題。可是,有沒有更好的方法呢?函數
heapq 模塊有兩個函數 nlargest() 和 nsmallest() 能夠完美解決這個問題。性能
In [50]: import heapq In [51]: n = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2, 23, 45, 76] In [52]: heapq.nlargest(3, n) Out[52]: [76, 45, 42] In [53]: heapq.nsmallest(3, n) Out[53]: [-4, 1, 2]
若是是取排在前面的 10% 應該怎麼作?測試
heapq.nlargest(round(len(n)/10), n)
並且,使用這兩個函數還會有更好的性能,由於在底層實現裏面,會先把數據進行堆排序後放入一個列表中,而後再進行後續操做。你們若是對堆數據結構感興趣的話,能夠繼續進行深刻研究,因爲我瞭解的並不深,也沒辦法再展開了。設計
可是也並非何時都是這兩個函數效果更好,好比只取一個最大值或者最小值,那仍是 min() 或 max() 效果更好;若是要查找的元素個數已經跟集合元素個數接近時,那仍是用 sorted(items)[:N] 更好,具體狀況具體分析吧。code
之前碰到這類問題時,我都會手動建立一個字典,而後以列表中元素做爲 key,進而統計出 key 出現的次數,再進行比較獲得出現次數最多的元素。排序
卻不知 collections 中就有專門爲這類問題設計的類 Counter,瞬間感受本身蠢爆了,話很少說,直接上代碼。ip
In [54]: from collections import Counter In [55]: w = ['a', 'b', 'c', 'd', 'a', 'a', 'b'] In [56]: w_count = Counter(w) In [57]: w_count Out[57]: Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1}) In [58]: w_count['a'] Out[58]: 3 In [59]: top = w_count.most_common(2) In [60]: top Out[60]: [('a', 3), ('b', 2)]
能夠看到,Counter 返回的就是一個字典,想知道哪一個元素出現幾回,直接取,是否是很方便?
並且還有 most_common 函數,簡直不要太棒。
有一個列表,以下:
In [61]: a = [1, 2, 3, 4, 5, -3]
要求過濾全部負數。須要新建一個列表?直接一行代碼搞定。
In [64]: [n for n in a if n > 0] Out[64]: [1, 2, 3, 4, 5]
若是要把負數替換成 0 呢?
In [67]: [n if n > 0 else 0 for n in a] Out[67]: [1, 2, 3, 4, 5, 0]
可是有時候過濾條件可能比較複雜,這時就須要藉助於 filter() 函數了。
values = ['1', '2', '-3', '-', '4', 'N/A', '5'] def is_int(val): try: x = int(val) return True except ValueError: return False ivals = list(filter(is_int, values)) print(ivals) # Outputs ['1', '2', '-3', '4', '5']
有下面這個字典:
rows = [ {'address': '5412 N CLARK', 'date': '07/01/2012'}, {'address': '5148 N CLARK', 'date': '07/04/2012'}, {'address': '5800 E 58TH', 'date': '07/02/2012'}, {'address': '2122 N CLARK', 'date': '07/03/2012'}, {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}, {'address': '1060 W ADDISON', 'date': '07/02/2012'}, {'address': '4801 N BROADWAY', 'date': '07/01/2012'}, {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}, ]
那麼怎麼對這個字典按照 date 進行分組呢?藉助於 itertools.groupby() 函數能夠解決這個問題,代碼以下:
# Sort by the desired field first rows.sort(key=itemgetter('date')) # Iterate in groups for date, items in groupby(rows, key=itemgetter('date')): print(date) for i in items: print(' ', i)
輸出結果以下:
07/01/2012 {'address': '5412 N CLARK', 'date': '07/01/2012'} {'address': '4801 N BROADWAY', 'date': '07/01/2012'} 07/02/2012 {'address': '5800 E 58TH', 'date': '07/02/2012'} {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'} {'address': '1060 W ADDISON', 'date': '07/02/2012'} 07/03/2012 {'address': '2122 N CLARK', 'date': '07/03/2012'} 07/04/2012 {'address': '5148 N CLARK', 'date': '07/04/2012'} {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
須要注意的是,groupby() 函數僅僅檢查連續相同的元素,因此在分組以前,必定要先對數據,按照分組字段進行排序。若是沒有排序,便得不到想要的結果。
我經常有這樣的苦惱,就是有一個列表,而後經過下標來取值,取值時很認真的數所須要元素在第幾個,很怕取錯值。取到值後開始下面的運算。
一段時間以後,再看這段代碼,感受很陌生,已經忘了帶下標的值是什麼了,還須要從新看一下這個列表的由來,才找到回憶。
若是能有一個名稱映射到元素上就行了,直接經過名稱就能夠知道元素的含義。collections.namedtuple() 函數就能夠解決這個問題。
In [76]: from collections import namedtuple In [77]: subscriber = namedtuple('Subscriber', ['addr', 'joined']) In [78]: sub = subscriber('jonesy@example.com', '2012-10-19') In [79]: sub Out[79]: Subscriber(addr='jonesy@example.com', joined='2012-10-19') In [80]: sub.addr Out[80]: 'jonesy@example.com' In [81]: sub.joined Out[81]: '2012-10-19'
這樣就能夠經過名稱來取值了,代碼可讀性也更高。
須要注意的是,這種命名元祖的方式不能直接修改其中的值,直接修改會報錯
In [82]: a = namedtuple('SSS', ['name', 'shares', 'price']) In [83]: _a = a('yongxinz', 1, 2) In [84]: _a.shares = 4 ----------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-84-f62a5288a29a> in <module>() > 1 _a.shares = 4 AttributeError: can't set attribute
想要修改的話可使用 _replace() 函數。
In [85]: _a._replace(shares=4) Out[85]: SSS(name='yongxinz', shares=4, price=2)
可是還有一個疑問,若是這個列表元素比較多的話,那就須要定義不少的名稱,也比較麻煩,還有更好的方式嗎?
未完待續。。。
歡迎留言,或添加我我的微信 zhangyx6a 交流,不是微商。