前言
Python 内置模块collections, 目标是提供各种专门的集合数据类型来解决特定的编程问题。
本系列介绍其中的数据结构特点和使用方法, 以便遇到某些特定的问题,可以找到对应的数据来处理, 达到事半功倍的效果
namedtuple | 用于创建其子类的工厂函数tuple ,提供命名字段,允许按名称访问项目,同时保持按索引访问项目的能力 |
deque | 一个类似序列的集合,支持从序列的任一端有效地添加和删除项目 |
OrderedDict | 根据插入键的时间保持键值对排序的字典子类 |
ChainMap | 一个类似字典的类,允许将多个映射视为单个字典对象 |
⭐defaultdict | 一个字典子类,用于为缺失的键构建默认值并自动将它们添加到字典中 |
⭐Counter | 一个字典子类,支持方便地对序列或可迭代中的唯一项进行计数 |
本文介绍其中两个 defaultdict, Counter, 其他的可以参考
Python中的collections模块(二) 有序字典OrderedDict和链接字典ChainMap
Python中的collections模块(三) 命名元组namedtuple和双端队列deque
Python中的collections模块(四) UserString,UserList,和UserDict
1.使用Python计数
工作中一个常见的场景,你需要统计一个数据中某些元素出现的次数。最常见的方法就是,便利目标数据,然后使用一个字典来存储元素和其出现次数的对应关系。
In [1]: tar = 'abcedabcdabcaba'
In [2]: counter = {}
In [3]: for letter in tar:
...: if letter not in counter:
...: counter[letter] = 1
...: else:
...: counter[letter] += 1
In [4]: counter
Out[4]: {'a': 5, 'b': 4, 'c': 3, 'e': 1, 'd': 2}
遍历每一个字符都要判断一下,该字符是否已经在counter中存在,比较麻烦。
一个比较优雅的方式是使用dict.get方法,遇到不存在的key不会抛出异常,并返回一个默认值。
另一个方式是使用collections.defaultdict方法,为counter字典设置默认值。
实现代码如下:
In [8]: tar = 'abcedabcdabcaba'
In [9]: counter = {}
In [10]: for letter in tar:
...: counter[letter] = counter.get(letter, 0) + 1
In [11]: counter
Out[11]: {'a': 5, 'b': 4, 'c': 3, 'e': 1, 'd': 2}
In [18]: from collections import defaultdict
In [19]: tar = 'abcedabcdabcaba'
In [20]: counter = defaultdict(int)
In [21]: for letter in tar:
...: counter[letter] += 1
In [22]: counter
Out[22]: defaultdict(int, {'a': 5, 'b': 4, 'c': 3, 'e': 1, 'd': 2})
使用defaultdict 解决方案更简洁易读。首先初始花counter
使用 int函数作为默认工厂函数对defaultdict进行初始化。这样,当访问不存在的键时,defaultdict
字典会自动创建该键并使用工厂函数返回的值对其进行初始化。即返回0
不论上述那种方式,都需要手动对目标数据进行遍历,那么有没有不需要手动遍历数据即可获取元素计数的方法呢? 答案是有的,与编程中的许多其他常见任务一样,Python 提供了一种更好的方法来解决计数问题, 那就是Counter,在collections模块中。
2. 入门Counter
Counter是dict的一个子类,通常使用一个序列或可迭代对象作为类构造函数的参数。
将对象中每一个元素作为key,出现的次数作为value
In [22]: from collections import Counter
In [23]: tar = 'abcedabcdabcaba'
In [24]: Counter(tar)
Out[24]: Counter({'a': 5, 'b': 4, 'c': 3, 'e': 1, 'd': 2})
# list作为参数
In [25]: l = ['aa', 'abc', 'bb', 'cc', 'abc', 'abc', 'bb', 'bb']
In [26]: Counter(l)
Out[26]: Counter({'aa': 1, 'abc': 3, 'bb': 3, 'cc': 1})
2.1 更新Counter中的数据
生成一个Counter 实例后,可以通过.update()
新的对象和计数来更新它, 虽然Counter是dict的一个子类,但是重写了update的功能。不是单纯的覆盖,二十将新的值和老的值相加,例子如下:
In [27]: c = Counter(tar)
In [28]: c
Out[28]: Counter({'a': 5, 'b': 4, 'c': 3, 'e': 1, 'd': 2})
# 更新Counter计数
In [29]: c.update('eeedddmmm')
In [30]: c
Out[30]: Counter({'a': 5, 'b': 4, 'c': 3, 'e': 4, 'd': 8, 'm': 6})
# 使用字典更新Counter计数
In [33]: c.update({'a':10, 'm': -5})
In [34]: c
Out[34]: Counter({'a': 15, 'b': 4, 'c': 3, 'e': 4, 'd': 8, 'm': 1})
2.2 访问Counter中的数据
因为Counter是dict的子类,可以使用字典访问元素的方法。另外Counter还提供了一个most_common方法,将Counter实例中的元素按照出现次数排序,返回一个列表。代码示例如下
In [36]: c
Out[36]: Counter({'a': 15, 'b': 4, 'c': 3, 'e': 4, 'd': 8, 'm': 1})
# 访问和赋值其中一个元素
In [37]: c['a']
Out[37]: 15
In [38]: c['c'] = 100
# 像dict一样使用keys values和 items方法
In [40]: c.keys()
Out[40]: dict_keys(['a', 'b', 'c', 'e', 'd', 'm'])
In [41]: c.values()
Out[41]: dict_values([15, 4, 100, 4, 8, 1])
In [42]: c.items()
Out[42]: dict_items([('a', 15), ('b', 4), ('c', 100), ('e', 4), ('d', 8), ('m', 1)])
most_common方法
In [44]: c.most_common()
Out[44]: [('c', 100), ('a', 15), ('d', 8), ('b', 4), ('e', 4), ('m', 1)]
In [45]: c.most_common(2)
Out[45]: [('c', 100), ('a', 15)]
# 按照出现次数倒序
In [46]: c.most_common()[::-1]
Out[46]: [('m', 1), ('e', 4), ('b', 4), ('d', 8), ('a', 15), ('c', 100)]
2.3 访问Counter对象的原始数据
Counter还提供了一个.elements方法, 返回一个迭代器,用以访问被计数的对象本身。代码:
In [47]: c
Out[47]: Counter({'a': 6, 'b': 4, 'c': 4, 'e': 4, 'd': 8, 'm': 1})
In [48]: print(list(c.elements()))
['a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'e', 'e', 'e', 'e', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'm']
3. 总结
本文介绍了,python中collections.Counter 类,可以更加优雅的处理需要计数的编码场景。