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

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

怎樣從一個集合中獲取最大或最小的N個元素列表?

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

import heapq
nums = [1,8,2,23,7,4,56,768,34,77,6,99]
print(heapq.nlargest(3,nums)) # prints [768, 99, 77]
print(heapq.nsmallest(3,nums))  # prints [1, 2, 4]
print(heapq.nlargest(round(len(nums)/10),nums))  #取前10%

兩個函數都能接受一個關鍵字參數,用於更復雜的數據結構中:

protfolio = [
    {'name': 'IBM', 'shares': 100, 'price': 91.1},
    {'name': 'AAPL', 'shares': 50, 'price': 543.22},
    {'name': 'FB', 'shares': 200, 'price': 21.90},
    {'name': 'HPQ', 'shares': 35, 'price': 31.75},
    {'name': 'YHOO', 'shares': 45, 'price': 16.35},
    {'name': 'ACME', 'shares': 75, 'price': 115.65},
]
cheap = heapq.nsmallest(3,protfolio,lambda s:s['price']) 
expensive = heapq.nlargest(3,protfolio,lambda s:s['price'])
【result】:
[{'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'FB', 'shares': 200, 'price': 21.9}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}]
[{'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'ACME', 'shares': 75, 'price': 115.65}, {'name': 'IBM', 'shares': 100, 'price': 91.1}]

當要查找的元素個數相對比較小的時候,函數nlargest()和nsmallest()是很適合的。如果你僅僅需要查找唯一最大或者最小的元素的話,那麼使用max()和min()函數會更快一些。
類似的,如果N的大小和和集合的大小接近,通常先排序這個集合然後再使用切片會更快點(sorted(items)[:N]或者sorted(items[-N:]))。需要在正確的場合使用函數nlargest()和nsmallest()函數才能出發揮它們的優勢。

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

怎樣找出一個序列中出現次數最多的元素呢?

collections.Counter類就是專門爲這個問題而設計的,它甚至有一個有用的most_common()方法直接給你答案。

from collections import Counter

words = [
    'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
    'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
    'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
    'my', 'eyes', "you're", 'under'
]

word_counts = Counter(words)
print(word_counts['the'])	# 5
# 出現次數最高的3個單詞
top_three = word_counts.most_common(3)
print(top_three)
# [('eyes', 8), ('the', 5), ('look', 4)]

如果需要手動實現計數,可以簡單的用加法:

morewords = ['why','are','you','not','looking','in','my','eyes']
for word in morewords:
	word_counts[word] += 1

或者也可以使用update()方法:

word_counts.update(morewords)

Counter實例一個鮮爲人知的特性就是它們可以很容易和數學運算操作相結合。比如:

>>> a = Counter(words)
>>> b = Counter(morewords)
>>> a
Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,
"you're": 1, "don't": 1, 'under': 1, 'not': 1})
>>> b
Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
'my': 1, 'why': 1})
>>> # Combine counts
>>> c = a + b
>>> c
Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2,
'around': 2, "you're": 1, "don't": 1, 'in': 1, 'why': 1,
'looking': 1, 'are': 1, 'under': 1, 'you': 1})
>>> # Subtract counts
>>> d = a - b
>>> d
Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,
"you're": 1, "don't": 1, 'under': 1})

3、通過某一個關鍵字排序一個字典列表

假設你有一個字典列表,想通過某個或某幾個關鍵字排序這個列表。

可以通過operator模塊的itemgetter函數,可以非常容易排序這樣的數據結構。

from operator import itemgetter

rows = [
    {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
    {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
    {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
    {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]

rows_by_fname = sorted(rows, key=itemgetter("fname"))
rows_by_uid = sorted(rows,key=itemgetter('uid'))
print(rows_by_fname)
print(rows_by_uid)

結果如下:

[{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'}]
[{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'}]

當需要根據排序的關鍵字有多個時,可以使用lambda函數

rows_by_fname = sorted(rows,key=lambda r:r['fname'])
row_by_lfname = sorted(rows,key=lambda r:(r['lname'],r['fname']))

這種方案也不錯。但是,使用itemgetter()方式會運行稍微快點。因此,如果你對性能要求較高的話,使用itemgetter()方式。

itemgetter()不僅適用於sorted同樣適用於max()min()

min(rows, key=itemgetter("uid"))
max(rows, key=itemgetter("uid"))

4、過濾序列元素

假如你有一個數據序列,想利用一些規則從中提取需要的值或者是縮短序列。

1.最簡單的方案是使用列表推導式

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> [n for n in mylist if n > 0]
[1, 4, 10, 2, 3]
>>> [n for n in mylist if n < 0]
[-5, -7, -1]
>>>

使用列表推導式的一個潛在缺陷就是如果輸入非常大的時候會產生一個非常大的結果集,佔用大量內存。
2.可以使用生成器表達式迭代產生過濾元素

>>> pos = (n for n in mylist if n > 0)
>>> pos
<generator object <genexpr> at 0x1006a0eb0>
>>> for x in pos:
... print(x)
...
14
10
23
>>>

當過濾元素比較複雜,不能簡單的在列表推導式或者生成器表達式中表達出來,這時候可以將過濾代碼放到一個函數中,然後使用內建的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']

引申:
列表推導式和生成器表達式還能過濾的時候轉換數據

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> import math
>>> [math.sqrt(n) for n in mylist if n > 0]
[1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
>>>

將不符合的值用新值代替

clip_neg = [n if n > 0 else 0 for n in mylist]
# [0,0,-5,0,-7,0,0,-1]

5、從字典中提取子集

假如你想構造一個字典,它是另一個字典的子集。

解決方案
  • 1.最簡單的是使用字典推導
prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}
p1 = {key: value for key, value in prices.items() if value > 20}

tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
p2 = {key: value for key, value in prices.items() if key in tech_names}
print(p1)
print(p2)
{'ACME': 45.23, 'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}
{'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}
  • 2.大多數情況下字典推導式能做到,通過創建一個元祖序列然後把它傳給 dict()函數也能實現
p3 = dict((key,value) for key, value in prices.items() if value > 200)

但是,字典推導式表達意思更清晰,並且實際上運行速度也更快些。

  • 3.有時候完成同一件事的方法有多中,比如,第二種例子也可以這樣重寫
tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
p4 = {key:prices[key] for key in prices.keys() & tech_names}

但是,運行時間測試顯示這種方案大概比第一種方案慢1.6倍。

6.通過某個關鍵字將記錄分組

假如你有一個字典或者實例序列,然後想根據某個特定字段來分組迭代訪問

解決方案:

  • itertools.groupby()函數對於這樣的數組分組操作非常實用。
from operator import itemgetter
from itertools import groupby

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'},
]

rows.sort(key=itemgetter('date'))

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()函數掃描整個序列並且查找連續相同的值(或者根據指定的key函數返回值相同)的元素序列。
一個非常重要的步驟是要根據指定的字段將數據排序。因爲groupby()函數僅僅檢查連續的元素,如果事先沒有排序完成的話,分組函數將得不到想要的結果。

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