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

文章首發於知乎專欄,歡迎關注。
https://zhuanlan.zhihu.com/py...python

如下測試代碼所有基於 Python3微信

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

工做中有時會遇到這樣的需求,取出數據中前面 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 交流,不是微商。

相關文章
相關標籤/搜索