任务
需要统计不同元素出现的次数,并且根据它们的出现次数安排它们的顺序——比如,你想制作一个柱状图。
解决方案
柱状图,如果不考虑它在图形图像上的含义,实际上是基于各种不同元素(用Python的列表或字典很容易处理)出现的次数,根据对应值将键或索引排序。下面是dict的一个子类,它为了这种应用加入了两个方法:
class hist(dict):
def add(self,item,increment=1):
'''为item的条目增加计数'''
self[item] = increment + self.get(item,0)
def counts(self,reverse=False):
'''返回根据对应值排序的键的列表'''
aux = [ (self[k],k) for k in self]
aux.sort()
if reverse: aux.reverse()
return [k for v,k in aux]
如果想将元素的统计结果放到一个列表中,做法也非常类似:
class hist1(list):
def __init__(self,n):
'''初始化列表,统计几个不同项的出现'''
list.__init__(self,n*[0])
def add(self,item,increment=1):
'''为item的条目增加计数1'''
self[item] += increment
def counts(self,reverse=False):
'''返回根据对应值排序的索引的列表'''
aux = [(v,k) for k,v in enumerate(self)]
aux.sort()
if reverse: aux.reverse()
return [k for v,k in aux]
讨论
hist 的 add 方法展示了 Python用于统计任意(可哈希的)元素的常用方法,并使用 dict来记录次数。在类 hist1中,在一个普通的列表的基础上,我们采用了不同的方法,并在__init__中将所有的次数都设置成0,因而add 方法就变得更简单了。
counts 方法生成了一个键或者索引的列表,并且根据对应值进行了排序。这两个类针对的问题很类似,因此解决方式也几乎完全一样,都使用了前面 5.2 节和 5.3 节展示过的DSU。如果我们想要在自己的程序中使用这两个类,由于它们的相似性,我们应该进行代码重构,从中间分离出共性并置入一个单独的辅助函数_sorted_keys:
def _sorted_keys(container,keys,reverse):
'''返回 keys的列表,根据container中的对应值排序'''
aux = [(container[k],k) for k in keys]
aux.sort()
if reverse: aux.reverse()
return [k for v,k in aux]
然后实现各个类的 counts方法,其实就是对 _sorted_keys 函数进行一层很薄的封装:
class hist(dict):
...
def counts(self,reverse=False):
return _sorted_keys(self,self,reverse)
class hist1(list):
...
def counts(self,reverse=False):
return _sorted_keys(self,xrange(len(self)),reverse)
DSU在 Python 2.4中非常重要,前面5.2节和5.3节已经介绍过了,列表的 sort 方法和新的内建的 sorted 函数提供了一个快速的、原生的 DSU 实现。因此,在 Python2.4中_sorted_keys 还可以变得更简单快速:
def _sorted_keys(container,keys,reverse):
return sorted(keys,key=container,__getitem__,reverse=reverse)
被绑定的 container.__getitem__方法和 Python2.3中实现的获取索引的操作 container[k]所做的事情完全一样,但是对于我们正在排序的序列而言,它是一个可调用体,可以应用于序列中的每个元素,即命名的键,因此我们可以将它传递给内建sorted 函数的作为 key 关键字参数的值。Python 2.4还提供了一个简单直接的方法来获取字典元素根据值排序后的列表:
from operator import itemgetter
def dict_items_sorted_by_value(d,reverse=False):
return sorted(d,iteritems(),key=itemgetter(1),reverse=reverse)
如果想排序一个元素为子容器的容器,Python 2.4新出现的高级函数 operator.itermgette是一个很方便的提供 key 参数的方法,它可以针对每个子容器的特定元素建立键。这正是我们想要的,因为字典的条目实际上就是一个键和值构成的对(两元素的元组)的序列,所谓根据对应值排序,就是根据每个元组的第二个元素进行排序。回到本节的主题,下面是本节解决方案中的 hist类的一个使用示例:
sentence = '''Hello there this is a test. Hello there this was a test, but now it is not.'
words = sentence.split()
c = hist()
for word in words:c.add(word)
print("Ascending count:")
print c.counts()
print "Descending count:"
print c.counts(reverse = True)
上述代码片段产生了如下的输出:
Ascending count:
[(1,'but'),(1,'it'),(1,'not.'),(1,'now'),(1,'test,'),(1,'test.'),(1,'was'),(2,'He1lo'),(2,'a'),(2,'is'),(2,'there'),(2,'this')]
Descending count:
[(2,'this'),(2,'there'),(2,'is'),(2,'a'),(2,'Hello'),(1,'was'),(1,'test.'),(1,'test,'),(1,'now'),(1,'not.'),(1,'it'),(1,'but')]