19、Python之容器:快来数一数,24678?Counter能数得更好

引言

关于数据的分组计数,前面的文章中已经涉及了很多次。眼下要进行分组计数,我们可用的方法有:

1、直接使用dict进行计数,需要对首次出现的键进行判断初始化的操作;

2、使用dict的setdefault()方法进行计数,代码可以简化一些,虽然方法名有点怪;

3、defaultdict进行计数,可以设置自动初始化,代码更加简洁。

虽然前面这三个方法都可以实现计数的需求,但是,还是停留在相对基础的计数功能。

如果我们想要更加灵活的计数功能,比如计完数之后,取出Top N,比如分别计数之后的合并......

前面的基础计数功能,倒不是不能实现这些需求,只是相对来说,有些麻烦了。我们可以试试collections中的Counter类,一个专门为计数而生的工具类。

Counter定义

Counter是collections模块下的一个工具类,我们已经介绍过的defaultdict也是该模块中的。

首先看下Counter的定义文档:

可以看到Counter是dict的子类,用于统计可哈希的元素的个数。其实,从文档中,已经能学到关于Counter的主要功能了。

需要注意的是,统计的是可哈希的元素,这点可能会限制了Coutner的使用范围,但是,结合defaultdict,还是可以实现更加灵活的计数。

要使用Counter,还要看下我们怎么能够构造一个Counter对象。看下文档中,魔术方法:__init__的说明:

可以看到,我们有几种主要的构造方式:

1、无参形式,直接构造一个空对象,然后在后续逐步更新。

2、传入一个可迭代的对象,一般是原始数据明细,比如字符串、列表、元组等,会自动对可迭代对象计数。

3、传入一个字典对象,一般是已经有的计数结果,用于对计数结果进行更进一步的扩充。

4、以关键字参数的形式传参,如同字典一样,只是已有的计数结果的存储形式可能不同而已。

所以,对于不可哈希的元素,我们可以通过dict或者defaultdict进行计数,然后用统计结果进行Counter对象的构造。

Counter的核心用法

1、简单的可哈希数据元素的计数并取出Top N

首先我们以单词计数为例,首先我们先生成一个列表,存放各个单词,然后对这些单词进行统计计数,最终输出单词的统计结果,并给出出现最多的Top 3。

直接看代码:

python 复制代码
from collections import Counter
import random

# 生成测试数据
words = [random.choice(['Python', 'Java', 'C++', 'Rust', 'Go', 'Ruby', 'Swift', 'Lua']) for _ in range(1000)]

# 构造Counter计数器
c = Counter(words)
# 输出统计结果
print(c)
# 获取Python的出现次数
print(c['Python'])
# 获取Top 3
print(c.most_common(3))

执行结果:

代码中,我们使用list存放所有的单词数据,然后用其构造一个Counter对象。

Counter对象可以像dict一样,使用[key]方式索引元素的出现次数。

most_common(n)方法,以列表的形式,返回Top n的数据,列表中的元素为元组形式,分别为键值和对应的次数。

复杂的计数场景

有些场景下,计数结果并没有我们所看到的这么简单。

如果数据存储在了两个列表中,或者数据量比较大,我们是分布式统计的,以便于提高统计的效率,就会涉及到每个局部统计的结果合并的问题。

如果使用dict或者defaultdict来处理这种情况,可能又涉及到键值判断或者初始化默认值的情况,但是用Counter则会变得简单很多,可以直接进行两个Counter的数学运算。

2、Counter对象的增量迭代式计数

首先看,增量式统计,数据是一批批陆续到来的,我们要使用Counter的update()方法进行计数的增量更新:

python 复制代码
from collections import Counter

# 第一批测试数据
words1 = ['Python'] * 10
words1.extend(['Java'] * 5)
# 构造计数器对象
c = Counter(words1)
# 输出当前统计结果
print(c)

# 等待一段时间,第二批数据来了
words2 = ['Python', 'Java', 'C++', 'Rust']
# 更新计数
c.update(words2)
print(c)

3、Counter对象的数学运算,实现局部计数结果的合并

大数据计算中,一个典型的入门案例,就是分布式word count。将海量数据切分为多个任务,分布到不同节点上分别进行统计,获取中间结果,然后将结合汇聚,生成最终的结果。

我们简单模拟一下,只是为了演示Counter的数学运算:

python 复制代码
from collections import Counter

# 假如数据量比较大,可能需要拆分成多个子任务,并行
# 统计子任务1
words1 = ['Python'] * 10
words1.extend(['Java'] * 5)
# 构造计数器对象
c1 = Counter(words1)
# 输出当前统计结果
print(c1)

# 统计子任务2
words2 = ['Python', 'Java', 'C++', 'Rust']
c2 = Counter(words2)
print(c2)

# 合并获取最终结果
c = c1 + c2
print(c)

执行结果:

当然,Coutner也是支持减法、交集、并集等运算的,这里就不一一演示了,感兴趣的,可以自行尝试。

4、不可哈希元素计数

针对不可哈希的元素的统计计数,我们可以结合dict、defaultdict得出统计结果,如果后续需要进行Top N的处理,或者统计结果的合并,再交由Counter进行进一步加工处理。

也可以使用列表生成式,将不可哈希的元素转换为可哈希的元素,然后进行统计。

总结

本文主要就关于统计计数的相关实现方式,重点介绍了Counter的基本使用。其实,这些都不需要记忆,只要有需求场景时,能想到有这么个工具类,实际使用时,再查看定义文档即可。

相关推荐
湫ccc27 分钟前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
哭泣的眼泪4081 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust