如何确定一个列表是否包含另一个列表?

5
有没有一种内置的Pythonic方法可以确定一个列表是否完全包含另一个列表的内容,包括重复的条目,但忽略项目的顺序?
>>> l1 = [2, 2, 3]
>>> l2 = [2, 2]
>>> l3 = [3, 2]
>>> l4 = [2, 2, 2]
>>> l5 = [2, 5, 2]

>>> is_superset(l1, l2)
True
>>> is_superset(l1, l3)
True
>>> is_superset(l1, l4)
False
>>> is_superset(l1, l5)
False

3
@Patrick:那个问题是关于子序列的测试;而这个问题正在寻找一个子多重集的测试。 - user2357112
如果集合A是集合B的子集,则B是A的超集。 - Patrick
all([l1.count(item) >= l2.count(item) for item in l2])?虽然非常低效。 - CristiFati
我不确定你所指的“包括重复条目”的缩进。你的意思是l1是l3的超集,但l3不是l1的超集吗? - abarnert
@abarnert 恰是如此。 - Chuck
4个回答

10

如果没有重复的元素,或者重复的元素不重要(也就是说,如果你的l1l3都是彼此的超集),那么你只需要使用集合。但是,由于如果你想让l1成为l3的真超集,你需要考虑多重集。幸运的是,Counter已经为你实现了多重集:

from collections import Counter
def is_superset(a, b):
    return not Counter(b) - Counter(a)

请注意,这个 - 是多重集之间的差(就像 - 是集合之间的差),而不是字典之间的逐元素减法。因此,如果你从一个超级多重集中减去另一个多重集,你会得到一个空的多重集(也就是说,Counter(),像 Python 中的所有空集合一样,是假值)。

现在:

>>> is_superset(l1, l2)
True
>>> is_superset(l1, l3)
True
>>> is_superset(l1, l4)
False
>>> is_superset(l1, l5)
False

另外:

>>> is_superset(l3, l1)
False

从未听说过它,bool(Counter(..)) 的语义是什么?是否有文档页面介绍它? - Ben Usman
1
@BenUsman 这与 Python 中的 每个 集合相同:空集合为假,非空集合为真。 (有一些第三方代码中的准集合,最著名的是 numpy 数组,在其中不成立,但 Counter 不是其中之一。) - abarnert
@BenUsman 请参考内置函数中的布尔运算,以及在回退到__len__时的__bool__。除此之外,我猜它没有明确定义,但是Counter被定义为“字典子类”这一事实_暗示_它应该遵循与字典相同的规则,就像它暗示len函数可以使用一样。如果你不相信这个,可以看看源代码 - abarnert
空集合的虚假性确实很直观。我不知道Counter支持所有这些代数操作,就像set一样。 - Ben Usman

4

以下是使用Counter的解决方案:

from collections import Counter

def is_superset(l1, l2):
    c1, c2 = Counter(l1), Counter(l2)
    return all(c1[k] >= c2[k] for k in c2)

1
我认为您不需要检查 c1 中的 k - Mark Dickinson
好的,忘记了计数器会为不存在的键提供0。谢谢。 - bphi
1
只需减去计数器即可。这不是逐元素的减法,而是多重集合的减法,因此如果你从一个超级(多重)集合中减去,你将得到一个空的多重集合。或者像fferri的答案中所述,交叉它们。无论哪种方式,如果你要将计数器用作多重集合,请利用它作为直观多重集合操作的优势。 - abarnert

3

使用Counter和内置的交集方法的另一种解决方案:

from collections import Counter

def is_superset(l1, l2):
    c1, c2 = Counter(l1), Counter(l2)
    return c1 & c2 == c2

测试:

>>> l1 = [2, 2, 3]
>>> l2 = [2, 2]
>>> l3 = [3, 2]
>>> l4 = [2, 2, 2]
>>> l5 = [2, 5, 2]
>>> is_superset(l1, l2)
True
>>> is_superset(l1, l3)
True
>>> is_superset(l1, l4)
False
>>> is_superset(l1, l5)
False
>>>

为什么每当 @fferri 发布问题时,就会有人立刻对其进行踩票? - Mihai Alexandru-Ionut
2
为了清晰起见,将 c1 & c2 加上括号可能是值得的;虽然 Python 的优先级将 & 放在比 == 更高的优先级上,但许多其他语言则相反。 - user2357112
另一种方式是读取 return c1 & True,这没有意义。 - fferri
我认为在这里交集可能比集合差异更清晰,所以我不知道为什么有人会对你的回答进行负评而不是我的。也许只是因为他们是C用户,并且被@user2357112建议澄清的观点所困惑了? - abarnert

1
这里提供一种低效的解决方案,它验证子列表中每个元素的出现次数必须小于或等于其在超级列表中的出现次数:
def is_superset(l1, l2):
    return all([l1.count(item) >= l2.count(item) for item in l2])

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