Python Counter作为袋子类型的比较

5 投票
3 回答
2468 浏览
提问于 2025-04-19 21:20

我需要在Python中使用一个像袋子或多重集合那样的数据类型。我知道collections.Counter通常是用来实现这个功能的。但是,比较运算符似乎不起作用:

In [1]: from collections import Counter

In [2]: bag1 = Counter(a=1, b=2, c=3)

In [3]: bag2 = Counter(a=2, b=2)

In [4]: bag1 > bag2
Out[4]: True

我觉得这像是个bug。我原本期待小于和大于运算符能像集合那样进行子集和超集的比较。但是如果真是这样的话,bag1 > bag2就应该是假的,因为bag2多了一个'a'。而且Counter对象上似乎也没有子集和超集的方法。所以我有两个问题:

  1. Counter对象使用了什么样的比较逻辑?
  2. 我该如何比较Counter对象的子集、超集、真子集和真超集?

3 个回答

1

与其使用 all 来遍历 Counter 的值,我们可以这样实现“多重集合包含”检查:

from collections import Counter
from functools import total_ordering

class PartiallyOrderedCounter(Counter):
    def __le__(self, other):
        """ Multiset inclusion """
        test = self.copy()
        test.subtract(other)
        return not -test
    
    def __lt__(self, other):
        """ Multiset strict inclusion """
        return self <= other and self != other

这样做可能效率稍低,但足够有趣,值得一提。

它的工作原理是:Counter 的一元取反实现(还有二元 - 操作符)会把结果中的每个计数值都取反,然后去掉所有负数或零的值。而 .subtract 方法则是在原地操作(需要复制一份),但允许结果中有负数。因此,当我们对减法的结果取反时,就得到了一个只包含在 other 中“缺失”值的 Counter(这些负数计数被取反成正数)。只有在没有这样的值时,计数器对象才会被视为假,这时我们通过使用 not 逻辑运算符返回 True

感谢 @PM2Ring 提供这个技巧

1

这个没有答案的问题很有意思:

我该如何比较计数器对象,以判断它们是子集、超集、真子集还是真超集呢?

可以通过定义缺失的“丰富比较方法”来解决这个问题。你也可以使用一些自由函数,这样可以让使用这些函数的代码更加清晰。

from collections import Counter

class PartiallyOrderedCounter(Counter):

    def __le__(self, other):
        """ Multiset inclusion """
        return all( v <= other[k] for k,v in self.items() )


    def __lt__(self, other):
        """ Multiset strict inclusion """
        return self <= other and self != other


    # TODO : __ge__ and __gt__
    # Beware : they CANNOT be written in terms of __le__ or __lt__


a = PartiallyOrderedCounter('abc')
b = PartiallyOrderedCounter('ab')
c = PartiallyOrderedCounter('abe')

assert a <= a
assert not a < a    
assert b <= a
assert b < a
assert not a < b    
assert not c <= a
assert not a <= c
2

在Python 2中,比较会退回到字典的默认排序规则Counterdict的一个子类)。

字典(映射)只有在它们的排序后的(键,值)列表相等时,才会被认为是相等的。[5] 除了相等以外的结果会一致处理,但没有其他定义。[6]

在Python 3中,比较会引发一个TypeError错误

字典(映射)只有在它们拥有相同的(键,值)对时,才会被认为是相等的。顺序比较(比如'<','<=','>=','>')会引发TypeError错误。

撰写回答