目录
一、绪论
二、Counter 类
2.1 创建 Counter 对象
2.2 数值访问(键-值索引)
2.3 元素删除 (键-值对删除)
2.4 update() / subtract() —— 元素更新 (键-值对增减)
2.5 elements() —— 元素全部列出
2.6 most_common() —— 元素按出现频数从大到小列出
2.7 算术与集合运算
2.8 常用操作范例
collections 作为 Python 的内建集合模块,实现了许多十分高效的特殊容器数据类型,即除了 Python 通用内置容器: dict、list、set 和 tuple 等的替代方案。在 IDLE 输入 help(collections) 可查看帮助文档,其中常见的类/函数如下:
名称 | 功能 |
namedtuple | 用于创建具有命名字段的 tuple 子类的 factory 函数 (具名元组) |
deque | 类似 list 的容器,两端都能实现快速 append 和 pop (双端队列) |
ChainMap | 类似 dict 的类,用于创建多个映射的单视图 |
Counter | 用于计算 hashable 对象的 dict 子类 (可哈希对象计数) |
OrderedDict | 记住元素添加顺序的 dict 子类 (有序字典) |
defaultdict | dict 子类调用 factory 函数来提供缺失值 |
UserDict | 包装 dict 对象以便于 dict 的子类化 |
UserList | 包装 list 对象以便于 list 的子类化 |
UserString | 包装 string 对象以便于 string 的子类化 |
而本文详述的对象为 hashable 对象计数器 —— Counter 。
Counter,顾名思义是一个计数器。Counter 类作为一个无序的容器类型,以字典的 key-value 对形式存储元素,旨在统计各元素 (哈希项) 出现的次数。具体而言,key 表示元素,value 表示各元素 key 出现的次数,可为任意整数 (即包括0与负数)。Counter 类有时被称为 bags 或 multisets 。我们在 IDLE 通过 help(collections.Counter) 可查看帮助文档。
在 Python 3.7 版更新后,作为 dict 的子类,Counter 继承了记住插入顺序的功能。 Counter 对象进行数学运算时同样会保持顺序。 结果会先按每个元素在运算符左边的出现时间排序,然后再按其在运算符右边的出现时间排序。
实例化 Counter 的常见方式为:
## 导入 Counter 类
>>> from collections import Counter # class collections.Counter([iterable-or-mapping])
>>> hashmap1 = Counter() # a new, empty counter
>>> hashmap1
Counter()
## 通过可迭代对象、映射和关键字参数等实例化 Counter 对象
>>> hashmap2 = Counter('happy') # a new counter from an iterable
>>> hashmap2
Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
>>> hashmap3 = Counter(['A', 'A', 'K', 4, 7, 7]) # a new counter from an iterable
>>> hashmap3
Counter({'A': 2, 'K': 1, 4: 1, 7: 2})
>>> hashmap4 = Counter(('M', 'M', 4, 'A', 1, 1)) # a new counter from an iterable
>>> hashmap4
Counter({'M': 2, 4: 1, 'A': 1, 1: 2})
>>> hashmap5 = Counter({'x':1, 'y':2}) # a new counter from a mapping
>>> hashmap5
Counter({'y': 2, 'x': 1})
>>> hashmap6 = Counter(kangkang=1, daming=2, sam=3) # a new counter from keyword args
>>> hashmap6
Counter({'sam': 3, 'daming': 2, 'kangkang': 1})
# 类型检查
>>> type(hashmap1), type(hashmap2), type(hashmap3), type(hashmap4), type(hashmap5), type(hashmap6)
(, , , , ), )
可见元素从一个 iterable 被计数或从其他的 mapping (or counter) 实现初始化。
Counter 对象类似于 dict 接口,也通过 key 索引访问 value,但注意返回的 value 表示的是 key 的计数值:
>>> hashmap2['p'] # hashmap2 = Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
2
>>> hashmap3['A'] # hashmap3 = Counter({'A': 2, 'K': 1, 4: 1, 7: 2})
2
>>> hashmap4['M'] # hashmap4 = Counter({'M': 2, 4: 1, 'A': 1, 1: 2})
2
>>> hashmap5['x'] # hashmap5 = Counter({'y': 2, 'x': 1})
1
而当访问的 key 不存在时,返回 0 而非 KeyError:
# count of a missing element is zero
>>> hashmap2[6] # hashmap2 = Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
0
>>> hashmap3[6] # hashmap3 = Counter({'A': 2, 7: 2, 'K': 1, 4: 1})
0
>>> hashmap4[6] # hashmap4 = Counter({'M': 2, 1: 2, 4: 1, 'A': 1})
0
>>> hashmap5[6] # hashmap5 = Counter({'y': 2, 'x': 1})
0
作为 dict 的子类,通过 Counter.keys()、Counter.values()、Counter.items() 访问键-值对仍然适用:
# 以 hashmap2 为例
>>> hashmap2 = Counter('happy')
>>> hashmap2
Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
>>> hashmap2.keys()
dict_keys(['h', 'a', 'p', 'y'])
>>> type(hashmap2.keys())
>>> hashmap2.values()
dict_values([1, 1, 2, 1])
>>> type(hashmap2.values())
>>> hashmap2.items()
dict_items([('h', 1), ('a', 1), ('p', 2), ('y', 1)])
>>> type(hashmap2.items())
注意,原地操作如 hashmap[key] += 1,值类型只支持加和减,故分数、小数、十进制甚至负值都适用。
对于 Counter 对象的元素删除应使用内置函数 del,而令 value=0 是不能删除元素(键-值对)的。
# 以 hashmap2 为例
>>> hashmap2 = Counter('happy')
>>> hashmap2
Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
>>> hashmap2['y'] = 0
>>> hashmap2
Counter({'p': 2, 'h': 1, 'a': 1, 'y': 0}) # 'y' 还在, 只是 value=0 而已
>>> del hashmap2['y']
>>> hashmap2
Counter({'p': 2, 'h': 1, 'a': 1}) # 'y' 被删除了
可以使用另外的 iterable 对象或 Counter 对象实现对原 Counter 对象的元素更新 (键-值对增减)。
其中,元素增加使用 Counter.update(iterable-or-mapping) 方法:
## 以 hashmap2 为例, 其余同理
>>> hashmap2 = Counter('happy')
>>> hashmap2
Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
## 用 iterable 对象更新
>>> hashmap2.update('hp') # 用 string 更新 (iterable 对象)
>>> hashmap2
Counter({'p': 3, 'h': 2, 'a': 1, 'y': 1})
>>> hashmap2.update(['a','y']) # 用 list 更新 (iterable 对象)
>>> hashmap2
Counter({'p': 3, 'h': 2, 'a': 2, 'y': 2})
>>> hashmap2.update(('a')) # 用 tuple 更新 (iterable 对象)
>>> hashmap2
Counter({'a': 3, 'p': 3, 'h': 2, 'y': 2})
## 用 mapping 对象更新
>>> hashmap2.update({'h':1, 'y':1}) # 用 dict 更新 (mapping 对象)
>>> hashmap2
Counter({'h': 3, 'a': 3, 'p': 3, 'y': 3})
## 用 Counter 对象更新
>>> hashmap2.update(Counter('hapy'))
>>> hashmap2
Counter({'h': 4, 'a': 4, 'p': 4, 'y': 4})
注意,元素的添加来自 iterable 对象计数元素,或另一个 mapping 对象 (或 Counter 对象) ,其中 iterable 对象应为序列 sequence 元素。
同理,元素减少使用 Counter.subtract(iterable-or-mapping) 方法,但注意元素的计数 —— value 为 0 和 负数都是允许的:
# 以 hashmap2 为例, 其余同理
>>> hashmap2 = Counter('happy')
>>> hashmap2
Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
# 用 iterable 对象更新元素
>>> hashmap2.subtract('p') # 用 string 更新
>>> hashmap2
Counter({'h': 1, 'a': 1, 'p': 1, 'y': 1})
>>> hashmap2.subtract(['p']) # 用 list 更新
>>> hashmap2
Counter({'h': 1, 'a': 1, 'p': 0, 'y': 1}) # 允许 value <= 0
>>> hashmap2.subtract(('p')) # 用 tuple 更新
>>> hashmap2
Counter({'h': 1, 'a': 1, 'y': 1, 'p': -1}) # 允许 value <= 0
## 用 mapping 对象更新
>>> hashmap2.subtract({'h':2}) # 用 dict 更新
>>> hashmap2
Counter({'a': 1, 'y': 1, 'h': -1, 'p': -1}) # 允许 value <= 0
## 用 Counter 对象更新
>>> hashmap2.update(Counter('ay'))
>>> hashmap2
Counter({'a': 0, 'y': 0, 'h': -1, 'p': -1})
可见使用 iterable 对象更新原 Counter 对象之前,都会被隐式地转换为 Counter 对象,再把元素更新到原 Counter 对象上。
Counter.elements() 方法将返回一个迭代器。元素的计数有多少,在该迭代器中就包含多少个该元素。元素排列无确定顺序 元素按首次出现的顺序返回,且不含 value<= 0 的元素。
>>> hashmap7 = Counter('happy')
>>> hashmap7
Counter({'p': 2, 'h': 1, 'a': 1, 'y': 1})
>>> hashmap7.elements() # 返回迭代器
>>> list(hashmap7.elements()) # 显式类型转换
['a', 'a']
>>> tuple(hashmap7.elements()) # 显式类型转换
('a', 'a')
>>> str(hashmap7.elements()) # 那是不可能的...
''
>>> sorted(hashmap7.elements()) # 隐式类型转换
['a', 'a']
Counter.most_common([n]) 方法将返回一个 list,其中包含前 n 个出现频数最高的元素及其出现次数,并从大到小排列。 若 n 被省略或为 None,most_common() 将返回 Counter 对象中的所有元素。若元素计数值相等,则按首次出现的顺序排序:
>>> hashmap8 = Counter(x=3, y=2, z=1, h=2)
>>> hashmap8
Counter({'x': 3, 'y': 2, 'h': 2, 'z': 1})
>>> hashmap8.most_common(1) # n=1
[('x', 3)]
>>> hashmap8.most_common(2) # n=2
[('x', 3), ('y', 2)]
>>> hashmap8.most_common(3) # n=3
[('x', 3), ('y', 2), ('h', 2)]
>>> hashmap8.most_common(4) # n=4
[('x', 3), ('y', 2), ('h', 2), ('z', 1)] # 'y' 与 'h' 出现频次均为 2, 但 'y' 先出现, 故前排
>>> hashmap8.most_common() # n ignored
[('x', 3), ('y', 2), ('h', 2), ('z', 1)] # 全排:从大到小
>>> hashmap8.most_common()[::-1] # n ignored
[('z', 1), ('h', 2), ('y', 2), ('x', 3)] # 全排:从小到大
有几个常用的数学运算操作:
>>> h1 = Counter(x=1, y=2)
>>> h2 = Counter(x=2, y=1)
>>> h3 = Counter(x=1, y=-1)
>>> h1 + h2
Counter({'x': 3, 'y': 3}) # 按元素相加
>>> h2 + h3
Counter({'x': 3}) # value <= 0 的会被删除
>>> h1 - h2
Counter({'y': 1}) # 按元素相减
>>> h1 - h3
Counter({'y': 3}) # value <= 0 的会被删除
>>> h1 & h2
Counter({'x': 1, 'y': 1}) # 按元素取 min() (交集)
>>> h1 | h2
Counter({'x': 2, 'y': 2}) # 按元素取 max() (并集)
还有单目加和减 (一元操作符) 及其等价形式:
>>> hashmap9 = Counter(a=2, b=1, c=0, d=-1)
>>> +hashmap9
Counter({'a': 2, 'b': 1}) # 去除 value<=0 的元素
>>> -hashmap9
Counter({'d': 1}) # 去除 value>=0 的元素
>>> hashmap9 += Counter()
>>> hashmap9
Counter({'a': 2, 'b': 1}) # 去除 value<=0 的元素
>>> hashmap8 = Counter(x=3, y=2, z=1, h=2)
>>> hashmap8
Counter({'x': 3, 'y': 2, 'h': 2, 'z': 1})
## 对 Counter 对象的 value 求和
>>> sum(hashmap8.values())
8
## 从小到大排序
>>> hashmap8.most_common()[::-1]
[('z', 1), ('h', 2), ('y', 2), ('x', 3)]
## 类型转换
>>> list(hashmap8)
['x', 'y', 'z', 'h'] # 将 Counter 对象的 key 转换为 list
>>> tuple(hashmap8)
('x', 'y', 'z', 'h') # 将 Counter 对象的 key 转换为 tuple
>>> set(hashmap8)
{'z', 'y', 'x', 'h'} # 将 Counter 对象的 key 转换为 set
>>> dict(hashmap8)
{'x': 3, 'y': 2, 'z': 1, 'h': 2} # 将 Counter 对象的转换为 dict
## 去除 value <= 0 的元素
>>> hashmap9 = Counter(a=2, b=1, c=0, d=-1)
>>> hashmap9
Counter({'a': 2, 'b': 1, 'c': 0, 'd': -1})
>>> hashmap9 += Counter()
>>> hashmap9
Counter({'a': 2, 'b': 1})
参考文献
《Python Immediate》
https://docs.python.org/zh-cn/3/library/collections.html#collections.Counter
https://www.liaoxuefeng.com/wiki/1016959663602400/1017681679479008