1.4 找到最大或最小的N个元素
问题:想在某个集合中找出最大或最小的N个元素
解决方案:
heapq模块的基本情况如下:
在Python中对堆这种数据结构进行了模块化,可通过调用heapq模块来建立堆,同时heapq模块也提供了相应的方法来对堆做操作。基本方法如下:
heap = [] #创建了一个空堆
heappush(heap,item) #往堆中插入一条新的值
item = heappop(heap)#从堆中弹出最小值
item = heap[0] #查看堆中最小值,不弹出
heapify(x) #以线性时间将一个列表转化为堆
item = heapreplace(heap,item) #弹出并返回最小值,然后将heapqreplace方法中item的值插入到堆中,堆的整体结构不会发生改变。
heapq模块中有两个函数——nlargest()和nsmallest()。例如:
>>> import heapq
>>> nums=[1,8,2,23,7,-4,18,23,42,37,2]
>>> print(heapq.nlargest(3,nums))
[42, 37, 23]
>>> print(heapq.nsmallest(2,nums))
[-4, 1]
两个print函数分别输出了nums中最大的3个值和最小的2个值。
这两个函数都可以接受一个参数key,从而允许它们工作在更加复杂的数据结构之上。例如:
>>> portfolio=[{'name':'IBM','shares':100,'price':91.1},\
{'name':'AAPL','shares':50,'price':543.22},\
{'name':'FB','shares':200,'price':21.09},\
{'name':'HPQ','shares':35,'price':31.75},\
{'name':'YHOO','shares':45,'price':16.35}]
>>> cheap=heapq.nsmallest(2,portfolio,key=lambda s: s['price'])
>>> cheap
[{'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'FB', 'shares': 200, 'price': 21.09}]
讨论
如果正在寻找最大或最小N个元素,且同几何中元素的总数目相比,N很小,则下列函数可提供更好的性能。这些函数首先会在底层将数据转化成列表,且元素会以堆的顺序排序。
nums=[1,8,2,23,7,-4,18,23,42,37,2]
>>> heap=list(nums)
>>> heap
[1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> heapq.heapify(heap)
>>> heap
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
堆的重要特性:heap[0]总是最小元素。此外,接下来的元素可依次通过 heapq.heappop() 方法轻松找到。该方法会将第一个元素弹出,然后以第二小的元素取而代之。
当所要找的元素数量相对较小时,函数nlargest() 和 nsmallest() 才是最适用的。若只想找到最大值和最小值,则应使用max()和min()。
1.5 实现优先级队列
问题:想要实现一个队列,能够以给定的优先级对元素排序,且每次pop操作时都会返回优先级最高的元素
解决方案
下面的类利用heapq模块实现了一个简单的优先级队列:
import heapq
class PriorityQueue:
def __init__(self):
self._queue=[]
self._index=0
def push(self,item,priority):
heapq.heappush(self._queue,(-priority,self._index,item))
self._index+=1
def pop(self):
return heapq.heappop(self._queue)[-1]
class Item:
def __init__(self,name):
self.name=name
def __repr__(self):
return 'Item({!r})'.format(self.name)
下面是对其的应用:
q=PriorityQueue()
q.push(Item('foo'),1)
q.push(Item('bar'),5)
q.push(Item('spam'),4)
q.push(Item('grok'),1)
q.pop()
Item('bar')
q.pop()
Item('spam')
q.pop()
Item('foo')
若两个有着相同优先级的元素,pop操作按照它们被插入到队列的顺序返回。
讨论
上面的代码中,队列包含了一个(-priority,index,item)的元组。优先级为负数的目的是使得元素按照优先级从高到低排序。这与普通的按优先级从低到高排序的堆排序恰巧相反。
index变量的作用是保证同等优先级元素的正确排序。通过保存一个不断增加的index下标变量,可以确保元素按照它们插入的顺序排序。而且,index变量也在相同优先级元素比较的时候起到重要作用。
1.6 字典中的键映射多个值
问题:怎样实现一个键对应多个值的字典(multidict)
解决方案
若想要一个键映射多个值,需将这多个值放到另外的容器中,比如列表或集合中。若想保持元素插入的顺序,则使用列表,若需要数据去重,则使用集合。
可以很方便的使用collections模块中的default来构造这样的字典。defaultdict的一个特征是它会自动初始化每个key刚开始对应的值,所以只需关注添加元素操作。例如:
>>> from collections import defaultdict
>>> d=defaultdict(list)
>>> d['a'].append(1)
>>> d['a'].append(2)
>>> d['a'].append(4)
>>> d['b'].append(5)
>>> d
defaultdict(<class 'list'>, {'a': [1, 2, 4], 'b': [5]})
1.7 字典排序
问题:创建一个字典,并在迭代或序列化这个字典时能控制元素的顺序。
解决方案
可使用collections模块中的OrderedDict类。在迭代操作时它会保持元素被插入时的顺序。示例如下:
>>> from collections import OrderedDict
>>> d=OrderedDict()
>>> d['foo']=1
>>> d['bar']=2
>>> d['spam']=3
>>> d['grok']=4
>>> d
OrderedDict([('foo', 1), ('bar', 2), ('spam', 3), ('grok', 4)])
>>> d
OrderedDict([('foo', 1), ('bar', 2), ('spam', 3), ('grok', 4)])
当想构建一个将来需要序列化或编码成其他格式的映射时,OrderedDict很有用。
讨论
OrderedDict内部维护这一个根据键插入顺序排序的双向链表。每次当一个新的元素插入进来时,它会被放到链表的尾部。对于一个已经存在的键的重复赋值不会改变键的顺序。
一个OrderedDict的大小是一个普通字典的两倍,因其内部维护着另外一个链表。
1.8 字典的运算
问题:怎样在数据字典中执行一些计算操作,如求最大值、最小值、排序等。
解决方案
考虑下面的股票名称和价格映射字典:
>>> prices={\
'ACME':45.23,
'AAPL':612.78,
'IBM':205.55,
'HPQ':37.20,
'FB':10.75}
为了对字典值进行计算操作,通常需使用zip()函数先将键和值反转。例如,下面是查找最小和最大股票价格和股票值的代码:
>>> min_price=min(zip(prices.values(),prices.keys()))
>>> min_price
(10.75, 'FB')
>>> max_price=max(zip(prices.values(),prices.keys()))
>>> max_price
(612.78, 'AAPL')
类似地,可使用zip()和sorted()排序:
>>> prices_sorted=sorted(zip(prices.values(),prices.keys()))
>>> prices_sorted
[(10.75, 'FB'), (37.2, 'HPQ'), (45.23, 'ACME'), (205.55, 'IBM'), (612.78, 'AAPL')]
需要注意的是,zip()函数创建的是一个只能方位一次的迭代器。
讨论
如果在一个字典上执行普通的数学运算,会发现它们仅仅作用于键,而不是值。而前面的zip()函数方案通过将字典反转为(值,键)元组序列来解决上述问题。当比较两个元组时,值会先进行比较,然后才是键。
但当多个实体拥有相同的值时,键会决定返回结果。
1.9 查找两字典的相同点
问题:怎样在两个字典中寻找相同点(比如相同的键,值等)
解决方案
考虑下列字典:
>>> a={
'x':1,
'y':2,
'z':3}
>>> b={
'w':10,
'x':11,
'y':2}
为寻找两个字典的相同点,可以简单的在两字典的keys()或者items()方法返回结果上执行集合操作。例如:
>>> a.keys()&b.keys()
{'y', 'x'}
>>> a.keys()-b.keys()
{'z'}
>>> a.items()&b.items()
{('y', 2)}
这些操作也可用于修改或过滤字典元素。比如,假如你想以现有字典构造一个排除几个指定键的新字典。下面利用字典推导来实现这样的需求:
>>> c={key:a[key] for key in a.keys()-{'z','w'}}
>>> c
{'y': 2, 'x': 1}
讨论
键视图的一个很少被了解的特性即它们也支持集合操作,比如集合并、交、差运算。故若想对集合的键执行一些普通的集合操作,可直接使用键视图对象而不必先将它们转换成一个set。
字典的items()方法的返回值也支持集合操作。但values()方法的返回值不支持。