Python 的计数器:计算对象的 Pythonic 方法

举报
Yuchuan 发表于 2021/08/16 08:44:14 2021/08/16
【摘要】 当你需要在 Python 中计算多个重复的对象时,你可以使用Counterfrom collections。此类提供了一种高效的 Pythonic 方法来计算事物,而无需使用涉及循环和嵌套数据结构的传统技术。这可以使您的代码更清晰、更快。

目录

一次计算几个重复的对象是编程中的一个常见问题。Python 提供了大量工具和技术,您可以使用它们来解决这个问题。然而,Python 的Counterfromcollections提供了一个干净、高效和 Pythonic 的解决方案。

这个字典子类提供了开箱即用的高效计数功能。Counter作为一名 Python 开发人员,理解以及如何有效地使用它是一项方便的技能。

在本教程中,您将学习如何:

  • 一次计算几个重复的对象
  • 使用 Python 创建计数器 Counter
  • 检索计数器中最常见的对象
  • 更新对象计数
  • 使用Counter以方便进一步的计算

您还将了解Counter用作multiset的基础知识,这是 Python 中此类的附加功能。

在 Python 中计算对象

有时您需要对给定数据源中的对象进行计数,以了解它们出现的频率。换句话说,您需要确定它们的频率。例如,您可能想知道特定项目在列表或值序列中出现的频率。当您的清单很短时,计算项目可以简单快捷。然而,当你有一个很长的清单时,计算事情可能更具挑战性。

要对对象进行计数,您通常使用counter,它是一个初始值为零的整数变量。然后增加计数器以反映给定对象在输入数据源中出现的次数。

当您计算单个对象的出现次数时,您可以使用单个计数器。但是,当您需要计算多个不同的对象时,您必须创建与您拥有的唯一对象一样多的计数器。

要一次计算多个不同的对象,您可以使用 Python 字典。字典将存储您要计数的对象。字典将保存给定对象的重复次数,或对象的计数

例如,要使用字典对序列中的对象进行计数,您可以遍历序列,检查当前对象是否不在字典中以初始化计数器(键值对),然后相应地增加其计数。

下面是一个计算单词“Mississippi”中字母的例子:

>>> word = "mississippi"
>>> counter = {}

>>> for letter in word:
...     if letter not in counter:
...         counter[letter] = 0
...     counter[letter] += 1
...

>>> counter
{'m': 1, 'i': 4, 's': 4, 'p': 2}

for循环遍历的字母word。在每次迭代中,条件语句都会检查手头的字母是否已经不是您使用 as 的字典中的键counter。如果是,它会创建一个带有该字母的新键并将其计数初始化为零。最后一步是将计数加一。当您访问 时counter,您会看到字母用作键,值用作计数。

注意:当您使用 Python 字典计算多个重复对象时,请记住它们必须是可散列的,因为它们将用作字典键。可散列意味着您的对象必须具有在其生命周期中永远不会改变的散列值。在 Python 中,不可变对象也是可散列的。

使用字典计算对象的另一种方法是使用dict.get()with0作为默认值:

>>> word = "mississippi"
>>> counter = {}

>>> for letter in word:
...     counter[letter] = counter.get(letter, 0) + 1
...

>>> counter
{'m': 1, 'i': 4, 's': 4, 'p': 2}

当您以.get()这种方式调用时,您将获得给定 的当前计数letter,或者0(默认值)如果缺少字母。然后将计数增加1,并将其存储letter在字典中的对应项下。

您还可以使用defaultdictfromcollections来计算循环中的对象:

>>> from collections import defaultdict

>>> word = "mississippi"
>>> counter = defaultdict(int)

>>> for letter in word:
...     counter[letter] += 1
...

>>> counter
defaultdict(<class 'int'>, {'m': 1, 'i': 4, 's': 4, 'p': 2})

该解决方案更简洁易读。您首先counter使用defaultdictwithint()作为默认工厂函数进行初始化。这样,当您访问底层 中不存在的键时,defaultdict字典会自动创建该键并使用工厂函数返回的值对其进行初始化。

在此示例中,由于您int()用作工厂函数,因此初始值为0,这是int()不带参数调用的结果。

与编程中的许多其他常见任务一样,Python 提供了一种更好的方法来解决计数问题。在 中collections,您会发现一个专门设计用于一次性计算多个不同对象的类。这个类被方便地称为Counter.

Python 入门 Counter

Counterdict专为计算 Python 中可散列对象而设计的子类。它是一个字典,将对象存储为键并计数为值。为了计算Counter,您通常提供一个序列或可迭代对象的可迭代对象作为类构造函数的参数。

Counter在内部迭代输入序列,计算给定对象出现的次数,并将对象存储为键,将计数存储为值。在下一节中,您将了解构造计数器的不同方法。

构造计数器

有几种方法可以创建Counter实例。但是,如果您的目标是一次计算多个对象,则需要使用序列或可迭代对象来初始化计数器。例如,您可以使用Counter以下方法重写 Mississippi 示例:

>>> from collections import Counter

>>> # Use a string as an argument
>>> Counter("mississippi")
Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})

>>> # Use a list as an argument
>>> Counter(list("mississippi"))
Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})

Counter迭代"mississippi"并生成一个字典,其中字母作为键,它们的频率作为值。在第一个示例中,您使用一个字符串作为 的参数Counter。您还可以使用列表、元组或任何具有重复对象的可迭代对象,如您在第二个示例中看到的。

注意:在 中Counter,高度优化的C 函数提供计数功能。如果此函数由于某种原因不可用,则该类使用等效但效率较低的Python 函数

还有其他方法可以创建Counter实例。但是,它们并不严格意味着计数。例如,您可以使用包含键和计数的字典,如下所示:

>>> from collections import Counter

>>> Counter({"i": 4, "s": 4, "p": 2, "m": 1})
Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})

计数器现在有一组初始键计数对。Counter当您需要提供现有对象组的初始计数时,这种创建实例的方法很有用。

您还可以在调用类的构造函数时使用关键字参数产生类似的结果:

>>> from collections import Counter

>>> Counter(i=4, s=4, p=2, m=1)
Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})

同样,您可以使用此方法Counter为其键和计数对创建具有特定初始状态的对象。

在实践中,如果您使用Counter从头开始计数,那么您不需要初始化计数,因为默认情况下它们的值为零。另一种可能性可能是将计数初始化为1。在这种情况下,您可以执行以下操作:

>>>
>>> from collections import Counter

>>> Counter(set("mississippi"))
Counter({'p': 1, 's': 1, 'm': 1, 'i': 1})

Python集合存储唯一的对象,因此set()本示例中的 调用会抛出重复的字母。在此之后,您最终会得到原始迭代中每个字母的一个实例。

Counter继承了常规词典的接口。但是,它没有提供.fromkeys()用于防止歧义的有效实现,例如Counter.fromkeys("mississippi", 2). 在这个特定的例子中,每个字母都有一个默认计数,2尽管它在输入迭代中的当前出现次数。

您可以存储在计数器的键和值中的对象没有限制。键可以存储可散列对象,而值可以存储任何对象。但是,要用作计数器,值应该是表示计数的整数。

这是一个Counter包含负计数和零计数的实例示例:

>>>
>>> from collections import Counter

>>> inventory = Counter(
...     apple=10,
...     orange=15,
...     banana=0,
...     tomato=-15
... )

在这个例子中,你可能会问,“为什么我有-15西红柿?” 嗯,这可能是一个内部约定,表明您有客户的15西红柿订单,而您当前的库存中没有任何西红柿。谁知道?Counter允许您执行此操作,并且您可能会找到该功能的一些用例。

更新对象计数

一旦你有了一个Counter实例,你就可以用.update()新的对象和计数来更新它。由提供dict.update()实现Counter将现有计数加在一起,而不是像其对应物那样替换值。必要时,它还创建新的键计数对。

您可以.update()将计数的迭代和映射用作参数。如果您使用可迭代对象,该方法会计算其项目并相应地更新计数器:

>>>
>>> from collections import Counter

>>> letters = Counter({"i": 4, "s": 4, "p": 2, "m": 1})

>>> letters.update("missouri")
>>> letters
Counter({'i': 6, 's': 6, 'p': 2, 'm': 2, 'o': 1, 'u': 1, 'r': 1})

现在您有 的6实例i, 的6实例s,等等。也有一些新的密钥数对,如'o': 1'u': 1'r': 1。请注意,iterable 需要是一个项目序列而不是一个(key, count)成对序列。

注意:您已经知道,您可以存储在计数器中的值(计数)没有限制。

使用整数以外的对象进行计数会破坏常见的计数器功能:

>>>
>>> from collections import Counter

>>> letters = Counter({"i": "4", "s": "4", "p": "2", "m": "1"})

>>> letters.update("missouri")
Traceback (most recent call last):
  ...
TypeError: can only concatenate str (not "int") to str

在此示例中,字母计数是字符串而不是整数值。这会中断.update(),导致TypeError.

第二种使用方法.update()是提供另一个计数器或计数映射作为参数。在这种情况下,您可以执行以下操作:

>>> from collections import Counter
>>> sales = Counter(apple=25, orange=15, banana=12)

>>> # Use a counter
>>> monday_sales = Counter(apple=10, orange=8, banana=3)
>>> sales.update(monday_sales)
>>> sales
Counter({'apple': 35, 'orange': 23, 'banana': 15})

>>> # Use a dictionary of counts
>>> tuesday_sales = {"apple": 4, "orange": 7, "tomato": 4}
>>> sales.update(tuesday_sales)
>>> sales
Counter({'apple': 39, 'orange': 30, 'banana': 15, 'tomato': 4})

在第一个示例中,您sales使用另一个计数器更新现有计数器monday_sales。请注意如何.update()添加来自两个计数器的计数。

注意:您还可以使用.update()with 关键字参数。因此,例如,执行类似sales.update(apple=10, orange=8, banana=3)的操作与sales.update(monday_sales)上面的示例相同。

接下来,您将使用包含项目和计数的常规字典。在这种情况下,.update()添加现有键的计数并创建缺少的键计数对。

访问计数器的内容

如您所知,Counter它的界面几乎与dict. 您可以使用计数器执行几乎与标准字典相同的操作。例如,您可以使用类似字典的键访问 ( [key])来访问它们的值。您还可以使用常用的技术和方法迭代键、值和项目:

>>> from collections import Counter

>>> letters = Counter("mississippi")
>>> letters["p"]
2
>>> letters["s"]
4

>>> for letter in letters:
...     print(letter, letters[letter])
...
m 1
i 4
s 4
p 2

>>> for letter in letters.keys():
...     print(letter, letters[letter])
...
m 1
i 4
s 4
p 2

>>> for count in letters.values():
...     print(count)
...
1
4
4
2

>>> for letter, count in letters.items():
...     print(letter, count)
...
m 1
i 4
s 4
p 2

在这些例子中,你存取并逐一使用熟悉的字典接口,其中包括方法,如键(字母)和您的计数器的值(计数).keys().values().items()

注意:如果您想更深入地了解如何遍历字典,请查看如何在 Python 中遍历字典。

最后要注意的一点Counter是,如果您尝试访问丢失的密钥,则会得到零而不是KeyError

>>>
>>> from collections import Counter

>>> letters = Counter("mississippi")
>>> letters["a"]
0

由于该字母"a"未出现在 string 中"mississippi",因此0当您尝试访问该字母的计数时,计数器会返回。

查找最常见的对象

如果您需要根据对象的频率或它们出现的次数列出一组对象,则可以使用.most_common(). 此方法返回(object, count)按对象的当前计数排序的列表。计数相等的对象按它们第一次出现的顺序出现。

如果您提供一个整数n作为 的参数.most_common(),那么您将获得n最常见的对象。如果省略n或将其设置为None,则.most_common()返回计数器中的所有对象:

>>>
>>> from collections import Counter
>>> sales = Counter(banana=15, tomato=4, apple=39, orange=30)

>>> # The most common object
>>> sales.most_common(1)
[('apple', 39)]

>>> # The two most common objects
>>> sales.most_common(2)
[('apple', 39), ('orange', 30)]

>>> # All objects sorted by count
>>> sales.most_common()
[('apple', 39), ('orange', 30), ('banana', 15), ('tomato', 4)]

>>> sales.most_common(None)
[('apple', 39), ('orange', 30), ('banana', 15), ('tomato', 4)]

>>> sales.most_common(20)
[('apple', 39), ('orange', 30), ('banana', 15), ('tomato', 4)]

在这些示例中,您使用.most_common()检索 中最频繁的对象sales。不带参数或带None,该方法返回所有对象。如果 to 的参数.most_common()大于当前计数器的长度,则您将再次获取所有对象。

您还可以通过对以下结果进行切片来获得最不常见的对象.most_common()

>>>
>>> from collections import Counter
>>> sales = Counter(banana=15, tomato=4, apple=39, orange=30)

>>> # All objects in reverse order
>>> sales.most_common()[::-1]
[('tomato', 4), ('banana', 15), ('orange', 30), ('apple', 39)]

>>> # The two least-common objects
>>> sales.most_common()[:-3:-1]
[('tomato', 4), ('banana', 15)]

第一个切片,[::-1]sales根据它们各自的计数以相反的顺序返回所有对象。切片[:-3:-1]从 的结果中提取最后两个对象.most_common()。您可以通过更改切片运算符中的第二个偏移值来调整获得的最不常见对象的数量。例如,要获取三个最不频繁的对象,您可以更改-3-4,依此类推。

注意:查看Reverse Python Lists: Beyond .reverse() 和 reversed()以获得使用切片语法的实践示例。

如果您想.most_common()正常工作,请确保计数器中的值是可排序的。这是需要记住的,因为如前所述,您可以在计数器中存储任何数据类型。

付诸Counter行动

到目前为止,您已经了解了Counter在代码中创建和使用对象的基础知识。您现在知道如何计算每个对象在给定序列或可迭代对象中出现的次数。您还知道如何:

  • 创建具有初始值的计数器
  • 更新现有计数器
  • 获取给定计数器中出现频率最高的对象

在以下部分中,您将编写一些实际示例,以便更好地了解 Python 的有用性Counter

计算文本文件中的字母

假设您有一个包含一些文本的文件。您需要计算每个字母在文本中出现的次数。例如,假设您有一个pyzen.txt包含以下内容的文件:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

是的,这就是Python 之禅,一系列指导原则定义了 Python 设计背后的核心哲学。要计算每个字母在本文中出现的次数,您可以利用Counter编写如下函数

 1# letters.py
 2
 3from collections import Counter
 4
 5def count_letters(filename):
 6    letter_counter = Counter()
 7    with open(filename) as file:
 8        for line in file:
 9            line_letters = [
10                char for char in line.lower() if char.isalpha()
11            ]
12            letter_counter.update(Counter(line_letters))
13    return letter_counter

下面是这段代码的工作原理:

  • 第 5 行定义了count_letters(). 此函数将基于字符串的文件路径作为参数。
  • 第 6 行创建了一个空计数器,用于计算目标文本中的字母。
  • 第 7 行 打开输入文件进行读取,并在文件内容上创建迭代器。
  • 第 8 行开始一个循环,逐行遍历文件内容。
  • 第 9 到 11 行定义了一个列表推导式,使用.isalpha(). 理解在过滤字母之前将字母小写,以防止有单独的小写和大写计数。
  • 第 12 行调用.update()字母计数器来更新每个字母的计数。

要使用count_letters(),您可以执行以下操作:

>>> from letters import count_letters
>>> letter_counter = count_letters("pyzen.txt")

>>> for letter, count in letter_counter.items():
...     print(letter, "->", count)
...
t -> 79
h -> 31
e -> 92
z -> 1
  ...
k -> 2
v -> 5
w -> 4

>>> for letter, count in letter_counter.most_common(5):
...     print(letter, "->", count)
...
e -> 92
t -> 79
i -> 53
a -> 53
s -> 46

伟大的!您的代码计算给定文本文件中每个字母的频率。语言学家经常使用字母频率识别语言。例如,在英语中,对平均字母频率的研究表明,五个最常见的字母是“e”、“t”、“a”、“o”和“i”。哇!这几乎与您的结果相符!

使用 ASCII 条形图绘制分类数据

统计是另一个可以使用的领域Counter。例如,当您处理分类数据时,您可能想要创建条形图来可视化每个类别的观察数。条形图对于绘制此类数据特别方便。

现在假设您要创建一个函数,允许您在终端上创建 ASCII 条形图。为此,您可以使用以下代码:

# bar_chart.py

from collections import Counter

def print_ascii_bar_chart(data, symbol="#"):
    counter = Counter(data).most_common()
    chart = {category: symbol * frequency for category, frequency in counter}
    max_len = max(len(category) for category in chart)
    for category, frequency in chart.items():
        padding = (max_len - len(category)) * " "
        print(f"{category}{padding} |{frequency}")

在此示例中,print_ascii_bar_chart()采用一些 categorical data,计算每个唯一类别在数据 ( frequency)中出现的次数,并生成反映该频率的 ASCII 条形图。

使用此功能的方法如下:

>>> from bar_chart import print_ascii_bar_chart

>>> letters = "mississippimississippimississippimississippi"
>>> print_ascii_bar_chart(letters)
i |################
s |################
p |########
m |####

>>> from collections import Counter
>>> sales = Counter(banana=15, tomato=4, apple=39, orange=30)

>>> print_ascii_bar_chart(sales, symbol="+")
apple  |+++++++++++++++++++++++++++++++++++++++
orange |++++++++++++++++++++++++++++++
banana |+++++++++++++++
tomato |++++

第一次调用print_ascii_bar_chart()绘制输入字符串中每个字母的频率。第二个调用绘制每个水果的销售额。在这种情况下,您使用计数器作为输入。另请注意,您可以使用symbol更改条形图的字符。

注意:在上面的示例中,绘制图表时print_ascii_bar_chart()不会对frequency值进行标准化。如果您使用具有高frequency值的数据,那么您的屏幕将看起来像一堆符号。

创建条形图时,使用水平条可为类别标签留出足够的空间。条形图的另一个有用功能是可以根据数据的频率对数据进行排序。在本例中,您使用 对数据进行排序.most_common()

使用 Matplotlib 绘制分类数据

很高兴知道如何使用 Python 从头开始​​创建 ASCII 条形图。但是,在 Python 生态系统中,您可以找到多种绘制数据的工具。这些工具之一是Matplotlib

Matplotlib 是一个第三方库,用于在 Python 中创建静态、动画和交互式可视化。您可以安装从库中的PyPI使用pip像往常一样:

$ python -m pip install matplotlib

此命令在您的 Python环境中安装 Matplotlib 。安装库后,您可以使用它来创建条形图等。以下是使用 Matplotlib 创建最小条形图的方法:

>>> from collections import Counter
>>> import matplotlib.pyplot as plt

>>> sales = Counter(banana=15, tomato=4, apple=39, orange=30).most_common()
>>> x, y = zip(*sales)
>>> x
('apple', 'orange', 'banana', 'tomato')
>>> y
(39, 30, 15, 4)

>>> plt.bar(x, y)
<BarContainer object of 4 artists>
>>> plt.show()

在这里,您首先执行所需的导入。然后,您使用一些有关水果销售的初始数据创建一个计数器,并用于.most_common()对数据进行排序。

您用于zip()将 的内容解压缩sales为两个变量:

  1. x 持有水果清单。
  2. y 持有每个水果的相应销售单位。

然后使用plt.bar(). 运行时plt.show(),您会在屏幕上看到如下所示的窗口:

水果销售条形图

在此图表中,横轴显示每个独特水果的名称。同时,纵轴表示每个水果的销售单位数。

寻找样本的众数

在统计学中,该模式是数据的样品中的最频繁的值(或值)。例如,如果您有 sample [2, 1, 2, 2, 3, 5, 3],则模式是2因为它出现的频率最高。

在某些情况下,模式不是唯一值。考虑一下样本[2, 1, 2, 2, 3, 5, 3, 3]。这里有两种模式,23,因为它们出现的次数相同。

您将经常使用该模式来描述分类数据。例如,当您需要知道数据中哪个类别最常见时,该模式很有用。

要使用 Python 查找模式,您需要计算样本中每个值的出现次数。然后你必须找到最频繁的值(或多个值)。换句话说,出现次数最多的值。这听起来像是您可以使用Counterand做的事情.most_common()

注意:statistics标准库中的Python模块提供了计算多个统计量的函数,包括单峰多峰样本的模式。下面的例子只是为了展示它的有用性Counter

这是一个计算样本模式的函数:

# mode.py

from collections import Counter

def mode(data):
    counter = Counter(data)
    _, top_count = counter.most_common(1)[0]
    return [point for point, count in counter.items() if count == top_count]

在里面mode(),您首先计算每个观察在输入中出现的次数data。然后您使用.most_common(1)来获得最常见观察的频率。由于.most_common()以 形式返回元组列表(point, count),您需要在 index 处检索元组0,这是列表中最常见的。然后将元组解包为两个变量:

  1. _持有最常见的对象。使用下划线命名变量表明您不需要在代码中使用该变量,但需要将其用作占位符。
  2. top_count保存 中最常见对象的频率data

列表推导式将count每个对象的 与最常见的对象 的计数进行比较top_count。这允许您识别给定样本中的多种模式。

要使用此功能,您可以执行以下操作:

>>> from collections import Counter
>>> from mode import mode

>>> # Single mode, numerical data
>>> mode([2, 1, 2, 2, 3, 5, 3])
[2]

>>> # Multiple modes, numerical data
>>> mode([2, 1, 2, 2, 3, 5, 3, 3])
[2, 3]

>>> # Single mode, categorical data
>>> data = [
...     "apple",
...     "orange",
...     "apple",
...     "apple",
...     "orange",
...     "banana",
...     "banana",
...     "banana",
...     "apple",
... ]

>>> mode(data)
['apple']

>>> # Multiple modes, categorical data
>>> mode(Counter(apple=4, orange=4, banana=2))
['apple', 'orange']

你的mode()作品!它找到数值和分类数据的众数。它还适用于单模和多模样本。大多数情况下,您的数据将出现在一系列值中。但是,最后一个示例表明您还可以使用计数器来提供输入数据。

按类型计算文件

另一个有趣的例子Counter是计算给定目录中的文件,按文件扩展名或文件类型对它们进行分组。为此,您可以利用pathlib

>>> import pathlib
>>> from collections import Counter

>>> entries = pathlib.Path("Pictures/").iterdir()
>>> extensions = [entry.suffix for entry in entries if entry.is_file()]
['.gif', '.png', '.jpeg', '.png', '.png', ..., '.png']

>>> Counter(extensions)
Counter({'.png': 50, '.jpg': 11, '.gif': 10, '.jpeg': 9, '.mp4': 9})

在此示例中,您首先使用Path.iterdir(). 然后使用列表推导式构建一个包含.suffix目标目录中所有文件的扩展名 ( ) 的列表。最后,您使用文件扩展名作为分组标准来计算文件数。

如果您在计算机上运行此代码,那么您将根据Pictures/目录的内容获得不同的输出(如果它存在的话)。因此,您可能需要使用另一个输入目录才能使此代码工作。

使用Counter实例作为多重集

在数学中,多重集表示允许其元素的多个实例的集合的变。给定元素的实例数称为多重性。因此,您可以拥有像 {1, 1, 2, 3, 3, 3, 4, 4} 这样的多重集,但集合版本将仅限于 {1, 2, 3, 4}。

就像在数学中一样,常规 Python只允许唯一元素:

>>>
>>> # A Python set
>>> {1, 1, 2, 3, 3, 3, 4, 4}
{1, 2, 3, 4}

当您创建这样的集合时,Python 会删除每个数字的所有重复实例。因此,您将获得一个仅包含唯一元素的集合。

Python 支持多重集的概念CounterCounter实例中的键是唯一的,因此它们相当于一个集合。计数保存每个元素的多重性或实例数:

>>> from collections import Counter

>>> # A Python multiset
>>> multiset = Counter([1, 1, 2, 3, 3, 3, 4, 4])
>>> multiset
Counter({3: 3, 1: 2, 4: 2, 2: 1})

>>> # The keys are equivalent to a set
>>> multiset.keys() == {1, 2, 3, 4}
True

在这里,您首先使用Counter. 这些键等同于您在上面的示例中看到的集合。这些值包含集合中每个元素的多重性。

Counter实现了一堆可用于解决多个问题的多集功能。编程中 multiset 的一个常见用例是购物车,因为它可以包含每个产品的多个实例,具体取决于客户的需求:

>>> from collections import Counter

>>> prices = {"course": 97.99, "book": 54.99, "wallpaper": 4.99}
>>> cart = Counter(course=1, book=3, wallpaper=2)

>>> for product, units in cart.items():
...     subtotal = units * prices[product]
...     price = prices[product]
...     print(f"{product:9}: ${price:7.2f} × {units} = ${subtotal:7.2f}")
...
course   : $  97.99 × 1 = $  97.99
book     : $  54.99 × 3 = $ 164.97
wallpaper: $   4.99 × 2 = $   9.98

在本例中,您使用一个Counter对象作为多重集创建一个购物车。计数器提供有关客户订单的信息,其中包括多个学习资源。该for循环迭代通过计数器和计算subtotal每个product打印到您的屏幕。

为了巩固您将Counter对象用作多重集的知识,您可以展开下面的框并完成练习。完成后,展开解决方案框以比较结果。

现在您已经了解了多重集是什么以及 Python 如何实现它们,您可以看看它Counter提供的一些多重集功能。

从计数器恢复元素

Counter您将要了解的第一个多集功能是.elements(). 此方法返回一个迭代器,遍历多集(Counter实例)中的元素,重复每个元素的次数与其计数表示的一样:

>>> from collections import Counter

>>> for letter in Counter("mississippi").elements():
...     print(letter)
...
m
i
i
i
i
s
s
s
s
p
p

调用.elements()计数器的净效果是恢复用于创建计数器本身的原始数据。该方法返回元素(在本例中为字母),其顺序与它们首次出现在底层计数器中的顺序相同。自Python 3.7 起Counter记住其键的插入顺序作为从dict.

注意:您已经知道,您可以创建带有零计数和负计数的计数器。如果元素的计数小于 1,则.elements()忽略它。

文档字符串.elements()源代码文件中提供了使用此方法来计算从它的许多一个有趣的例子素因子。由于给定的质因数可能会出现多次,因此您可能最终会得到一个多重集。例如,您可以将数字 1836 表示为其质因数的乘积,如下所示:

1836 = 2 × 2 × 3 × 3 × 3 × 17 = 22 × 33 × 171

您可以将此表达式编写为多重集,如 {2, 2, 3, 3, 3, 17}。使用Python的Counter,你就会有Counter({2: 2, 3: 3, 17: 1})。一旦你有了这个计数器,你就可以使用它的素数来计算原始数字:

>>> from collections import Counter

>>> # Prime factors of 1836
>>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
>>> product = 1

>>> for factor in prime_factors.elements():
...     product *= factor
...

>>> product
1836

循环遍历 中的元素prime_factors并将它们相乘以计算原始数字1836。如果您使用的是Python 3.8或更高版本,则可以使用prod()frommath来获得类似的结果。此函数计算输入迭代中所有元素的乘积:

>>> import math
>>> from collections import Counter

>>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
>>> math.prod(prime_factors.elements())
1836

在这个例子中,调用.elements()恢复素因数。然后一次性从它们进行math.prod()计算1836,这样您就无需编写循环,也无需使用一些中间变量。

Using.elements()提供了一种恢复原始输入数据的方法。它唯一的缺点是,在大多数情况下,输入中项目的顺序与输出中的顺序不匹配:

>>> from collections import Counter

>>> "".join(Counter("mississippi").elements())
'miiiisssspp'

在此示例中,生成的字符串不会拼写原始单词mississippi。但是,它在字母方面具有相同的内容。

减去元素的多重性

有时您需要减去多重集或计数器中元素的多重性(计数)。在这种情况下,您可以使用.subtract(). 顾名思义,此方法从目标计数器中的计数中减去可迭代或映射中提供的计数。

假设您有一个包含当前水果库存的 multiset,您需要使其保持最新状态。然后您可以运行以下一些操作:

>>> from collections import Counter

>>> inventory = Counter(apple=39, orange=30, banana=15)

>>> # Use a counter
>>> wastage = Counter(apple=6, orange=5, banana=8)
>>> inventory.subtract(wastage)
>>> inventory
Counter({'apple': 33, 'orange': 25, 'banana': 7})

>>> # Use a mapping of counts
>>> order_1 = {"apple": 12, "orange": 12}
>>> inventory.subtract(order_1)
>>> inventory
Counter({'apple': 21, 'orange': 13, 'banana': 7})

>>> # Use an iterable
>>> order_2 = ["apple", "apple", "apple", "apple", "banana", "banana"]
>>> inventory.subtract(order_2)
>>> inventory
Counter({'apple': 17, 'orange': 13, 'banana': 5})

在这里,您使用多种方式将输入数据提供给.subtract()。在所有情况下,您都可以通过减去输入数据中提供的计数来更新每个唯一对象的计数。您可以将其.subtract()视为 的对应物.update()

用元素的多重性做算术

使用.subtract().update(),您可以通过减去和添加相应的元素计数来组合计数器。或者,Python 为元素计数的加法 ( +) 和减法 ( -) 以及交集 ( &) 和联合 ( |)提供了方便的运算符。的交叉点算子返回最小相应计数的,而联合操作者返回最大计数。

以下是所有这些运算符如何工作的几个示例:

>>> from collections import Counter

>>> # Fruit sold per day
>>> sales_day1 = Counter(apple=4, orange=9, banana=4)
>>> sales_day2 = Counter(apple=10, orange=8, banana=6)

>>> # Total sales
>>> sales_day1 + sales_day2
Counter({'orange': 17, 'apple': 14, 'banana': 10})

>>> # Sales increment
>>> sales_day2 - sales_day1
Counter({'apple': 6, 'banana': 2})

>>> # Minimum sales
>>> sales_day1 & sales_day2
Counter({'orange': 8, 'apple': 4, 'banana': 4})

>>> # Maximum sales
>>> sales_day1 | sales_day2
Counter({'apple': 10, 'orange': 9, 'banana': 6})

在这里,您首先使用加法运算符 ( +)将两个计数器相加。结果计数器包含相同的键(元素),而它们各自的值(重数)包含来自两个相关计数器的计数总和。

第二个示例显示减法运算符 ( -) 的工作原理。请注意,负计数和零计数导致结果计数器中不包括键计数对。因此,您orange在输出中看不到,因为 8 - 9 = -1。

交集运算符 ( &) 从两个计数器中提取计数较低的对象,而联合运算符 ( |) 从两个涉及的计数器中返回计数较高的对象。

注意:有关如何Counter处理算术运算的更多详细信息,请查看类文档

Counter还支持一些一元运算。例如,您可以分别使用加号 ( +) 和减号 ( -)获取具有正数和负数的项目:

>>> from collections import Counter

>>> counter = Counter(a=2, b=-4, c=0)

>>> +counter
Counter({'a': 2})

>>> -counter
Counter({'b': 4})

当您+在现有计数器上使用加号 ( ) 作为一元运算符时,您将获得计数大于零的所有对象。另一方面,如果使用减号 ( -),则会得到计数为负的对象。请注意,结果不包括在这两种情况下计数都为零的对象。

结论

当你需要在 Python 中计算多个重复的对象时,你可以使用Counterfrom collections。此类提供了一种高效的 Pythonic 方法来计算事物,而无需使用涉及循环和嵌套数据结构的传统技术。这可以使您的代码更清晰、更快。

在本教程中,您学习了如何:

  • 使用不同的 Python 工具计算几个重复的对象
  • 使用 Python 创建快速高效的计数器 Counter
  • 检索特定计数器中最常见的对象
  • 更新和操作对象计数
  • 使用Counter以方便进一步的计算

您还学习了将Counter实例用作多重集的基础知识。有了所有这些知识,您将能够快速计算代码中的对象并使用多重集执行数学运算。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。