Python的“collections.Counter”具有非整数值

2024-04-27 20:35:24 发布

您现在位置:Python中文网/ 问答频道 /正文

collections.Counter对于收集计数和将计数作为对象进行操作非常有用。你可以这样做:

>>> Counter({'a': 2, 'b': 5}) + Counter({'a': 3, 'c': 7})
Counter({'c': 7, 'a': 5, 'b': 5})

这本质上是按项的键对项进行分组,并对每组的值求和

使用非整数值重用Counter功能的最低代码方式是什么?

这些值将被定义为我已经想要的组值“减少”操作:例如字符串和列表(它们都将__add__定义为串联)

目前,我们得到:

>>> Counter({'a': 'hello ', 'b': 'B'}) + Counter({'a': 'world', 'c': 'C'})
Traceback (most recent call last):
    ...
TypeError: '>' not supported between instances of 'str' and 'int'
>>> Counter({'a': [1, 2], 'b': [3, 4]}) + Counter({'a': [1, 1], 'c': [5, 6, 7]})
Traceback (most recent call last):
    ...
TypeError: '>' not supported between instances of 'list' and 'int'

collections.Counter的代码中,有一个硬编码的假设,即值是整数,因此self.get(k, 0)count > 0之类的东西到处都是。因此,子类化Counter似乎不会比重写我自己的专用(或通用)自定义类(可能使用collections.defaultdict)少多少工作

相反,似乎包装值(例如strlist)以便能够像处理空元素一样处理0是一种优雅的方法


Tags: 代码most定义counternot整数betweencall
2条回答

我会提出两个解决方案:

一个是包装值本身,尽管这里只保证涵盖问题中的示例:其他Counter操作不是:

>>> class ZeroAsEmptyMixin:
...     _empty_val = None
...
...     def __gt__(self, other):
...         if isinstance(other, int) and other == 0:
...             other = self._empty_val
...         return super().__gt__(other)
...
...     def __add__(self, other):
...         if isinstance(other, int) and other == 0:
...             other = self._empty_val
...         return self.__class__(super().__add__(other))
...
...
>>> class mystr(ZeroAsEmptyMixin, str):
...     _empty_val = str()
...
...
>>> class mylist(ZeroAsEmptyMixin, list):
...     _empty_val = list()
...
...
>>>
>>> Counter({'a': mystr('hello '), 'b': mystr('B')}) + Counter({'a': mystr('world'), 'c': mystr('C')})
Counter({'a': 'hello world', 'c': 'C', 'b': 'B'})
>>> Counter({'a': mylist([1, 2]), 'b': mylist([3, 4])}) + Counter({'a': mylist([1, 1]), 'c': mylist([5, 6, 7])})
Counter({'c': [5, 6, 7], 'b': [3, 4], 'a': [1, 2, 1, 1]})

另一种方法是编写一个“类似计数器”的自定义类,将defaultdict子类化,然后再次实现__add__方法

>>> from collections import defaultdict
>>> from functools import wraps
>>>
>>> class MapReducer(defaultdict):
...     @wraps(defaultdict.__init__)
...     def __init__(self, *args, **kwargs):
...         super().__init__(*args, **kwargs)
...         self._empty_val = self.default_factory()
...
...     def __add__(self, other):
...         if not isinstance(other, self.__class__):
...             return NotImplemented
...         result = self.__class__(self.default_factory)
...         for elem, val in self.items():
...             newval = val + other[elem]
...             if newval != self._empty_val:
...                 result[elem] = newval
...         for elem, val in other.items():
...             if elem not in self and val != self._empty_val:
...                 result[elem] = val
...         return result
...
...
>>>
>>> strmp = lambda x: MapReducer(str, x)
>>> strmp({'a': 'hello ', 'b': 'B'}) + strmp({'a': 'world', 'c': 'C'})
MapReducer(<class 'str'>, {'a': 'hello world', 'b': 'B', 'c': 'C'})
>>> listmp = lambda x: MapReducer(list, x)
>>> listmp({'a': [1, 2], 'b': [3, 4]}) + listmp({'a': [1, 1], 'c': [5, 6, 7]})
MapReducer(<class 'list'>, {'a': [1, 2, 1, 1], 'b': [3, 4], 'c': [5, 6, 7]})

我不会说“为集合类型定义整数加法”比仅仅重写Counter来完成您需要的工作更优雅。你需要非计数相关的行为,你需要一个不专注于计数的类

基本上,Counter不适合您的用例;您没有计数。当您缺少每个键的乘法计数时,elements是什么意思most_common可能像写的那样工作,但它与频率无关

在95%的情况下,我只会使用collections.defaultdict(list)(或任何适当的默认值),在其他5%的情况下,我会使用Counter作为模型并实现我自己的版本(不计算特定的行为)

相关问题 更多 >