-3

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 进行操作,就好像它是一个空元素一样,这可能是一种优雅的方法。

4

2 回答 2

2

我不会称“为集合类型定义整数的加法”比仅仅重写Counter以完成您需要的工作更优雅。您需要与计数无关的行为,您需要一个不专注于计数的课程。

从根本上说,Counter不适合您的用例;你没有计数elements当您缺少将每个键乘以的计数时,这意味着什么?most_common可能会像写的那样工作,但它与频率无关。

在 95% 的情况下,我只会使用collections.defaultdict(list)(或任何适当的默认值),而在另外 5% 的情况下,我会Counter用作模型并实现我自己的版本(没有特定于计数的行为)。

于 2020-09-15T17:10:32.987 回答
0

我会提出两个解决方案:

一个包装值本身,尽管这里只保证问题中的示例:其他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]})

另一种方法是编写一个“Counter-like”的自定义类,子类化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]})
于 2020-09-15T17:35:55.810 回答