测试Python中的Counter是否包含在另一个Counter中。

13

如何测试一个 Python Counter 对象是否包含在另一个对象中,使用以下定义:

如果对于 Counter a 中的每个键 k,值 a[k] 都小于或等于 Counter b 中的值 b[k],则 Counter a 包含在 Counter b 中。例如,Counter({'a': 1, 'b': 1}) 包含在 Counter({'a': 2, 'b': 2}) 中,但不包含在 Counter({'a': 2, 'c': 2}) 中。

我认为这是一个糟糕的设计选择,但在 Python 2.x 中,比较运算符 (<, <=, >=, >) 不使用上述定义,因此第三个 Counter 被认为是大于第一个。然而在 Python 3.x 中,Counter 是不可排序类型。


2
你应该正确定义“contained”以避免混淆。 - Shashank
Counter 实际上不支持比较运算符。 - user2357112
1
@JimDennis:我们应该将计数器视为一个多重集合,而这种尝试并没有考虑到元素的多重性。 - user2357112
@JimDennis:不,我不想检查所有的键是否存在,我也想像user2347112说的那样检查多重性:当且仅当a中的每个键k的值a[k]小于或等于b[k]的值时,Counter a包含在Counter b中。 - enrico.bacis
5个回答

16

更新于2023年:Counter 支持 富比较运算符,自 Python 3.10 版本起可以使用,因此以下写法是可行的:

container <= contained

Python < 3.10的历史答案:

我想到的最好方法是将我在代码中给出的定义进行转换:

def contains(container, contained):
    return all(container[x] >= contained[x] for x in contained)

但感觉很奇怪,Python没有一个开箱即用的解决方案,我不得不为每个运算符编写一个函数(或创建一个通用函数并传递比较函数)。


3
同意,考虑到Counter提供了几个类似于多重集合的操作,令人惊讶的是它没有提供一个等价于“子集”的方法。我期望只要做counter_a <= counter_b就可以了。无论如何,这对我来说似乎是最好的解决方案。 - Gareth Latty

15
虽然 Counter 实例不能使用 <> 运算符进行比较,但您可以使用 - 运算符找到它们之间的差异。该差异永远不会返回负计数,因此,如果 A - B 为空,则说明 B 包含了 A 中的所有项。
def contains(larger, smaller):
    return not smaller - larger

4
考虑给出这个答案,但我决定不这样做,因为这个方法不能短路并且浪费了大量时间和空间来构建计数器。此外,由于-如何处理第二个参数中的负计数,必须遍历两个计数器。基于 all 的解决方案更有效率。 - user2357112

2

另外,表达这个概念的一种简洁的方式是:

“计数器A是计数器B的子集”等价于 (A & B) == A

这是因为两个计数器的交集 (&) 具有共同元素的计数。如果A 中的每个元素(包括多重计数)也在B中,则它将与A相同;否则它将更小。

就性能而言,这似乎与Blckknght提出的not A - B方法大致相同。如enrico.bacis所述的答案中所示,逐个检查每个键会更快。

作为一种变化,您还可以检查并集是否等于较大的计数器(因此没有添加任何内容):(A | B) == B。对于我测试的某些大型多重集合(1,000,000个元素),这明显较慢。


1
collections.Counter("abc") & collections.Counter("b") == collections.counter("abc") 返回 False,所以似乎不起作用。 - Dr-Bracket
这是因为“abc”不是“b”的子集。 - Nick Matteo

1

对于较小的Counter中的所有键,请确保没有任何值大于较大Counter中对应的值:

def containment(big, small):
    return not any(v > big[k] for (k, v) in small.iteritems())

>>> containment(Counter({'a': 2, 'b': 2}), Counter({'a': 1, 'b': 1}))
True
>>> containment(Counter({'a': 2, 'c': 2, 'b': 3}), Counter({'a': 2, 'b': 2}))
True
>>> print containment(Counter({'a': 2, 'b': 2}), Counter({'a': 2, 'b': 2, 'c':1}))
False
>>> print containment(Counter({'a': 2, 'c': 2}), Counter({'a': 1, 'b': 1})
False

1
你使用的是哪个版本的Python?在Python 2中,两次调用都会返回“True”,而在Python 3中,它会抛出错误,因为Counter是不可排序的。此外,你实际上并不需要一个函数来完成这个任务。 - enrico.bacis
@enrico.bacis 我之前复制错了。在我使用的 Py 2.7 上可以正常工作。感谢你发现了这个问题! - Saksham Varma
@enrico.bacis 我已经添加了另一个检查来避免你提到的情况。 - Saksham Varma
现在它可以工作,但请记住 not any( predicate )all( not predicate ) 完全相同,因此答案在语义上与我上面的答案相同 ;) - enrico.bacis
另外还有几点建议,您正在使用.items()获取值v,而您应该更喜欢.iteritems(),它不会创建中间列表,而是生成器,因为您没有存储这些值。您也没有使用v。为了更有效率,您应该使用not any(v > big[k] for (k, v) in small.iteritems())或者not any(small[k] > big[k] for k in small) - enrico.bacis
显示剩余2条评论

1

虽然这些答案具有历史意义,但都已经过时了。 计数器类对象实际上是可比较的 在此输入图像描述


谢谢你的回答!不过我建议你把实际的代码写在回答里,而不是粘贴截图。这样可以让其他人直接复制粘贴你的代码,而不必手动输入。 - Shawn Hemelstrand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接