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()函数仅仅检查连续的元素,如果事先没有排序完成的话,分组函数将得不到想要的结果。

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