详解Python中非常好用的计数器Counter

详解Python中非常好用的计数器Counter

Counter简介

Counter是Python内置模块collections中的一个计数器工具,可以方便快捷地计数。

Counter是字典dict的子类,用于计数可哈希(hashable)对象。(Python中实现了魔法方法__hash__的对象是hashable对象,关于可哈希和不可哈希,可以自行搜索了解,后面有时间我可以再专门写文章详细介绍)

Counter是一个多项集,元素被存储为字典的键,元素的计数被存储为字典的值。计数可以是任何整数,包括零或负数。(甚至也可以是小数,不过本文不对此展开讨论。)

Counter计数器的优点:

  • 用法简单:它可以通过一个可迭代对象(iterable)来初始化,用一个映射(mapping)对象(包括Counter本身)来初始化,用键值对来初始化,或者直接创建一个空的Counter实例。

  • 访问不存在的元素不报错:Counter对象的接口类似于字典,不同的是,如果查询的键不在Counter中,它会返回0,而不是抛出KeyError异常。相当于对任意键都有一个默认值0,这可以在一些遍历搜索的代码中避免程序报错。(严格来说,不报错有利有弊,不过在计数这种场景利大于弊,程序设计团队肯定也权衡过。)

  • 支持的方法很多:作为dict的子类,Counter继承了dict的大部分方法,此外还实现了一些非常便利的方法。Counter也可以转换成列表、集合、字典等Python内置数据类型,从而使用这些数据类型的方法,如切片操作等。

接下来,本文将详细对Counter的各种特性和功能进行逐一介绍。

计数器功能实现

假设要统计一个列表[1, 2, 3, 4, 5, 1, 2, 3]中各元素出现的次数,可以利用字典,用如下代码实现。

# coding=utf-8
nums = [1, 2, 3, 4, 5, 1, 2, 3]
result = dict()
for n in nums:
    if n not in result:
        result[n] = 1
    else:
        result[n] += 1
print(result)

Output:

{1: 2, 2: 2, 3: 2, 4: 1, 5: 1}

上面的代码中我们自己新建了一个字典,将待计数的列表nums中的元素作为字典的键,遍历nums,对每个元素进行计数。

Counter的计数功能就类似于上面的代码,创建一个Counter对象即可完成计数。

from collections import Counter

nums = [1, 2, 3, 4, 5, 1, 2, 3]
print(Counter(nums))

Output:

Counter({1: 2, 2: 2, 3: 2, 4: 1, 5: 1})

可以看到,Counter封装了计数功能,只需要把待计数的数据传给Counter计数器,它即可立即返回一个计数器对象,完成对元素的计数。

在需要计数时,可以直接调用Counter来使用,不用再自己写计数的代码。虽然自己写代码也并不复杂,但是更关键的是,Counter除了可以完成计数,还实现了一些非常好用的方法,方便我们对计数器进行更丰富的处理和解析。

Counter初始化的几种方式

# 创建一个空计数器,然后用字典的方式给计数器添加值
c = Counter()
c['ai'] = 100
print(c)
# 实例化时将可迭代对象传入Counter
c1 = Counter('pythoncode')
print(c1)
# 实例化时将映射对象传入Counter
c2 = Counter({'a': 5, 'b': 2, 'c': 1})
print(c2)
# 实例化时将键值对传入Counter
c3 = Counter(A=1, B=2, C=5, D=6, E=7)
print(c3)

Output:

Counter({'ai': 100})
Counter({'o': 2, 'p': 1, 'y': 1, 't': 1, 'h': 1, 'n': 1, 'c': 1, 'd': 1, 'e': 1})
Counter({'a': 5, 'b': 2, 'c': 1})
Counter({'E': 7, 'D': 6, 'C': 5, 'B': 2, 'A': 1})

在实例化Counter时,可以传入可迭代对象(如字符串、列表),映射类型对象(如字典),键值对等。

访问Counter中不存在的元素

如果访问字典中不存在的键,会报错KeyError。例如:

t_dict = {'A': 0, 'K': 3, 'Q': 3, 'J': 1}
print(t_dict['KING'])

Output:

Traceback (most recent call last):
  File "..\counter_demo.py", line 36, in 
    print(t_dict['KING'])
KeyError: 'KING'

在Counter中访问不存在的元素时,会返回0。(这相当于字典用setdefault(key, value)设置了默认值,通过魔法方法__missing__实现。)

t_counter = Counter({'A': 0, 'K': 3, 'Q': 3, 'J': 1})
print(t_counter['KING'])

Output:

0

对于计数器中不存在的元素,返回的计数值为0,这比较符合生活常识,比报错KeyError友好很多,在一些代码中也有好处。

此外,前面提到过,Counter中的计数还可以是负数,通常情况,计数不会有负数,不过负数也不是完全没有用处,对于一些特殊场景,如需要记录类似“欠账”的情况,负数也可以派上用场。

这里还有一个注意点,Counter中某元素计数为0和Counter中不存在某元素,返回值都为0,代码处理(如访问元素的计数值、执行自增操作等)没有差别,但本质上并不一样,一种是Counter存在该元素,一种是Counter中不存在该元素。

也就是说,如果把Counter中的一个元素的计数值改成0,并不代表从Counter中删除了该元素。

t_counter['J'] = 0
print(t_counter)
print('J' in t_counter)
print('KING' in t_counter)

Output:

Counter({'K': 3, 'Q': 3, 'A': 0, 'J': 0})
True
False

要将元素从Counter中删除,跟字典删除方法相同,使用del关键字。

del t_counter['J']
print(t_counter)
print('J' in t_counter)

Output:

Counter({'K': 3, 'Q': 3, 'A': 0})
False

Counter支持的方法

Counter继承了字典的大部分方法,可以直接使用,如常用的keys()、values()、items(),还有copy()、clear()、pop()等等,就不逐一例举了。

本章节主要介绍Counter支持的一些附加方法。

elements(): 返回一个迭代器,其中每个元素都重复计数值所指定的次数。相同的元素会集中在一起,不同元素会按首次出现的顺序排列,如果一个元素的计数值小于1,elements()会将其忽略。

c_counter = Counter(['red', 'pink', 'yellow', 'blue', 'yellow', 'blue', 'pink', 'blue'])
print(c_counter.elements())
print(list(c_counter.elements()))

Output:


['red', 'pink', 'pink', 'yellow', 'yellow', 'blue', 'blue', 'blue']

most_common([n]): 返回一个列表,包含计数值最大的n个元素及其计数,按计数值从高到低排序,如果计数值相等,按元素首次出现的顺序排列。如果n为None(不传值或传None),返回计数器中的所有元素。

c_counter = Counter(['red', 'pink', 'yellow', 'blue', 'yellow', 'blue', 'pink', 'blue'])
print(c_counter.most_common())
print(c_counter.most_common(2))

Output:

[('blue', 3), ('pink', 2), ('yellow', 2), ('red', 1)]
[('blue', 3), ('pink', 2)]

subtract([iterable-or-mapping]): 减去一个可迭代对象或映射对象(包含Counter)中的元素(个数)。具体执行时将每个元素的计数值减去传入元素的计数值,输入输出都可以是0或负数。如果是不存在的元素,用0减。结果中元素的顺序按计数值从高到低排序,如果计数值相等,按元素首次出现的顺序排列。(可以发现,从文章开头,除elements()外,都是按这种顺序排序,本文后面的结果也都是按这种顺序。)

c_counter = Counter(['red', 'pink', 'yellow', 'blue', 'yellow', 'blue', 'pink', 'blue'])
print(c_counter)
ls = ['blue', 'yellow']
c_counter.subtract(ls)  # 减一个列表
print(c_counter)
dt = {'pink': 1, 'red': 1, 'green': 1}
c_counter.subtract(dt)  # 减一个字典
print(c_counter)
c2_counter = Counter({'blue': 1, 'yellow': 2, 'green': 3})
c_counter.subtract(c2_counter)  # 减另一个Counter
print(c_counter)

Output:

Counter({'blue': 3, 'pink': 2, 'yellow': 2, 'red': 1})
Counter({'pink': 2, 'blue': 2, 'red': 1, 'yellow': 1})
Counter({'blue': 2, 'pink': 1, 'yellow': 1, 'red': 0, 'green': -1})
Counter({'pink': 1, 'blue': 1, 'red': 0, 'yellow': -1, 'green': -4})

update([iterable-or-mapping]): 加上一个可迭代对象或映射对象(包含Counter)中的元素(个数)。具体执行时将每个元素的计数值加上传入元素的计数值,输入和输出都可以是0或负数。

c_counter.update(ls)  # 加一个列表
print(c_counter)
c_counter.update(dt)  # 加一个字典
print(c_counter)
c_counter.update(c2_counter)  # 加另一个Counter
print(c_counter)

Output:

Counter({'blue': 2, 'pink': 1, 'red': 0, 'yellow': 0, 'green': -4})
Counter({'pink': 2, 'blue': 2, 'red': 1, 'yellow': 0, 'green': -3})
Counter({'blue': 3, 'pink': 2, 'yellow': 2, 'red': 1, 'green': 0})

Counter的subtract()和update()是一对互逆的方法,类似于字典的update(),不过Counter中是计数值的相加或相减,而非更新替换。

此外,可迭代对象应当是一个元素序列,不能是一个(key, value)对组成的序列,那样会将整个序列当成一个整体元素处理。

total(): 返回计数器中总的计数值,此方法从Python3.10开始支持,更早的版本不支持。

c_counter = Counter(['red', 'pink', 'yellow', 'blue', 'yellow', 'blue', 'pink', 'blue'])
print(c_counter)
print(c_counter.total())

Output:

Counter({'blue': 3, 'pink': 2, 'yellow': 2, 'red': 1})
8

Counter的算术运算

cnt1 = Counter({'red': 3, 'green': 2, 'blue': 1})
cnt2 = Counter({'red': 1, 'green': 2, 'blue': 3})
print(cnt1 + cnt2)
print(cnt1 - cnt2)

Output:

Counter({'red': 4, 'green': 4, 'blue': 4})
Counter({'red': 2})

两个Counter计数器相加或相减,会将计数器中各元素的计数值进行加减,得到一个新的Counter计数器。

结果中只保留计数大于0的元素,也就是说算术运算前的计数器中可以有0或负数,但结果中不会保留0和负数。

Counter的交并运算

cnt1 = Counter({'red': 3, 'green': 2, 'blue': 1})
cnt2 = Counter({'red': 1, 'green': 2, 'blue': 3})
print(cnt1 | cnt2)  # 并集
print(cnt1 & cnt2)  # 交集

Output:

Counter({'red': 3, 'blue': 3, 'green': 2})
Counter({'green': 2, 'red': 1, 'blue': 1})

Counter的并集运算使用逻辑或的符号 | ,交集使用逻辑与的符号 & 。这里要注意,用Python的关键字 or 和 and 计算的结果不一样,or 和 and 会依次判断两个Counter是否为True,做的是逻辑运算不是交并运算。

计算并集时,结果取相同元素在两个Counter中计数值较大的计数,计算交集时,结果取相同元素在两个Counter中计数值较小的计数。

Counter的比较运算

cnt3 = Counter({'red': 20, 'green': 10, 'blue': 0})
cnt4 = Counter({'red': 20, 'green': 10})
print(cnt3 == cnt4)
print(cnt4 < cnt3)

Output:

True
False

Counter之间可以进行比较运算,支持 ==, !=, <, <=, >, >= 。在比较时,如果不存在的元素,会用计数0进行比较,所以上面的cnt3和cnt4相等。对于小于,只要Counter中有一个元素的计数值小于另一个Counter,其他元素的计数值可以相等,此时会返回True。大于同理。

比较运算从Python3.10开始支持,更早的版本不支持。

参考文档:https://docs.python.org/zh-cn/3/library/collections.html#counter-objects


相关阅读:

详解Python中的排列组合生成器

欢迎 点赞 收藏⭐ 评论 关注 如有错误敬请指正!

☟ 学Python,点击下方名片关注我。☟

你可能感兴趣的:(Python/PYPI,python,算法,Python计数器,Counter)