Python集合交集问题

发布于 2024-09-25 20:04:46 字数 254 浏览 1 评论 0原文

我有三个集合:

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]   # true, 16 and 14
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])]    # false

我想要一个函数,如果列表中的每个集合与列表中的至少一个其他集合相交,该函数将返回 True。是否有内置的或简单的列表理解?

I have three sets:

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]   # true, 16 and 14
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])]    # false

I want a function that will return True if every set in the list intersects with at least one other set in the list. Is there a built-in for this or a simple list comprehension?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

巴黎盛开的樱花 2024-10-02 20:04:46
all(any(a & b for a in s if a is not b) for b in s)
all(any(a & b for a in s if a is not b) for b in s)
篱下浅笙歌 2024-10-02 20:04:46

这是一个非常简单的解决方案,对于大输入非常有效:

def g(s):
    import collections
    count = collections.defaultdict(int)
    for a in s:
        for x in a:
            count[x] += 1
    return all(any(count[x] > 1 for x in a) for a in s)

Here's a very simple solution that's very efficient for large inputs:

def g(s):
    import collections
    count = collections.defaultdict(int)
    for a in s:
        for x in a:
            count[x] += 1
    return all(any(count[x] > 1 for x in a) for a in s)
梦在深巷 2024-10-02 20:04:46

这有点冗长,但我认为这是一个非常有效的解决方案。它利用了这样一个事实:当两个集合相交时,我们可以将它们标记为已连接。它通过保留与集合列表一样长的标志列表来实现这一点。当设置i和设置j相交时,它会为它们设置标志。然后,它循环遍历集合列表,并仅尝试查找尚未相交的集合的交集。读完评论后,我认为这就是@Victor 所说的。

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]   # true, 16 and 14
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])]    # false


def connected(sets):
    L = len(sets)

    if not L: return True
    if L == 1: return False

    passed = [False] * L
    i = 0
    while True:
        while passed[i]: 
            i += 1
            if i == L: 
                return True

        for j, s in enumerate(sets):
            if j == i: continue
            if sets[i] & s: 
                passed[i] = passed[j] = True
                break
        else:
            return False


print connected(s0)
print connected(s1)

我决定连接一个空的集合列表(如果您生成列表的一个元素,我可以生成一个它相交的元素;)。仅包含一个元素的列表可以轻松断开连接。如果您不同意,无论哪种情况,都可以更改一行。

It's a little verbose but I think it's a pretty efficient solution. It takes advantage of the fact that when two sets intersect, we can mark them both as connected. It does this by keeping a list of flags as long as the list of sets. when set i and set j intersect, it sets the flag for both of them. It then loops over the list of sets and only tries to find a intersection for sets that haven't already been intersected. After reading the comments, I think this is what @Victor was talking about.

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]   # true, 16 and 14
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])]    # false


def connected(sets):
    L = len(sets)

    if not L: return True
    if L == 1: return False

    passed = [False] * L
    i = 0
    while True:
        while passed[i]: 
            i += 1
            if i == L: 
                return True

        for j, s in enumerate(sets):
            if j == i: continue
            if sets[i] & s: 
                passed[i] = passed[j] = True
                break
        else:
            return False


print connected(s0)
print connected(s1)

I decided that an empty list of sets is connected (If you produce an element of the list, I can produce an element that it intersects ;). A list with only one element is dis-connected trivially. It's one line to change in either case if you disagree.

纵山崖 2024-10-02 20:04:46

这是一个更有效(如果更复杂)的解决方案,它执行线性数量的交集和数量为 O( n*log(n) ) 的并集,其中 n 是 s 的长度:

def f(s):
    import math
    j = int(math.log(len(s) - 1, 2)) + 1
    unions = [set()] * (j + 1)
    for i, a in enumerate(s):
        unions[:j] = [set.union(set(), *s[i+2**k:i+2**(k+1)]) for k in range(j)]
        if not (a & set.union(*unions)):
            return False
        j = int(math.log(i ^ (i + 1), 2))
        unions[j] = set.union(a, *unions[:j])
    return True

请注意,此解决方案仅适用于 Python >= 2.6。

Here's a more efficient (if much more complicated) solution, that performs a linear number of intersections and a number of unions of order O( n*log(n) ), where n is the length of s:

def f(s):
    import math
    j = int(math.log(len(s) - 1, 2)) + 1
    unions = [set()] * (j + 1)
    for i, a in enumerate(s):
        unions[:j] = [set.union(set(), *s[i+2**k:i+2**(k+1)]) for k in range(j)]
        if not (a & set.union(*unions)):
            return False
        j = int(math.log(i ^ (i + 1), 2))
        unions[j] = set.union(a, *unions[:j])
    return True

Note that this solution only works on Python >= 2.6.

两仪 2024-10-02 20:04:46

像往常一样,我想给出不可避免的 itertools 解决方案;-)

from itertools import combinations, groupby
from operator import itemgetter


def any_intersects( sets ):
    # we are doing stuff with combinations of sets
    combined = combinations(sets,2) 
    # group these combinations by their first set
    grouped = (g for k,g in groupby( combined, key=itemgetter(0)))
    # are any intersections in each group
    intersected = (any((a&b) for a,b in group) for group in grouped)
    return all( intersected )


s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])] 
print any_intersects( s0 ) # True
print any_intersects( s1 ) # False

这真的很懒,只会执行所需的交集。它也可能是一个非常令人困惑和难以阅读的单行文字;-)

As usual I'd like to give the inevitable itertools solution ;-)

from itertools import combinations, groupby
from operator import itemgetter


def any_intersects( sets ):
    # we are doing stuff with combinations of sets
    combined = combinations(sets,2) 
    # group these combinations by their first set
    grouped = (g for k,g in groupby( combined, key=itemgetter(0)))
    # are any intersections in each group
    intersected = (any((a&b) for a,b in group) for group in grouped)
    return all( intersected )


s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])] 
print any_intersects( s0 ) # True
print any_intersects( s1 ) # False

This is really lazy and will only do the intersections that are required. It can also be a very confusing and unreadable oneliner ;-)

在巴黎塔顶看东京樱花 2024-10-02 20:04:46

要回答您的问题,不,没有内置或简单的列表理解可以满足您的要求。这是另一个非常高效的基于 itertools 的解决方案 - 令人惊讶的是,在计时测试中使用 groupby() 的速度是 @THC4k 的 itertools 答案的两倍您的样本输入。它可能还可以进一步优化,但正如所呈现的那样,它的可读性非常好。就像@AaronMcSmooth一样,当输入列表中没有或只有一组时,我任意决定返回什么。

from itertools import combinations

def all_intersect(sets):
    N = len(sets)
    if not N: return True
    if N == 1: return False

    intersected = [False] * N
    for i,j in combinations(xrange(N), 2):
        if not intersected[i] or not intersected[j]:
            if sets[i] & sets[j]:
                intersected[i] = intersected[j] = True
    return all(intersected)

To answer your question, no, there isn't a built-in or simple list comprehension that does what you want. Here's another itertools based solution that is very efficient -- surprisingly about twice as fast as @THC4k's itertools answer using groupby() in timing tests using your sample input. It could probably be optimized a bit further, but is very readable as presented. Like @AaronMcSmooth, I arbitrarily decided what to return when there are no or is only one set in the input list.

from itertools import combinations

def all_intersect(sets):
    N = len(sets)
    if not N: return True
    if N == 1: return False

    intersected = [False] * N
    for i,j in combinations(xrange(N), 2):
        if not intersected[i] or not intersected[j]:
            if sets[i] & sets[j]:
                intersected[i] = intersected[j] = True
    return all(intersected)
硬不硬你别怂 2024-10-02 20:04:46

此策略不太可能像@Victor的建议那么有效,但可能比 jchl 的答案 由于集合算术(union)的使用增加。

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])]

def freeze(list_of_sets):
    """Transform a list of sets into a frozenset of frozensets."""
    return frozenset(frozenset(set_) for set_ in list_of_sets)

def all_sets_have_relatives(set_of_sets):
    """Check if all sets have another set that they intersect with.

    >>> all_sets_have_relatives(s0)   # true, 16 and 14
    True
    >>> all_sets_have_relatives(s1)   # false
    False
    """
    set_of_sets = freeze(set_of_sets)
    def has_relative(set_):
        return set_ & frozenset.union(*(set_of_sets - set((set_,))))
    return all(has_relative(set) for set in set_of_sets)

This strategy isn't likely to be as efficient as @Victor's suggestion, but might be more efficient than jchl's answer due to increased use of set arithmetic (union).

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])]
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])]

def freeze(list_of_sets):
    """Transform a list of sets into a frozenset of frozensets."""
    return frozenset(frozenset(set_) for set_ in list_of_sets)

def all_sets_have_relatives(set_of_sets):
    """Check if all sets have another set that they intersect with.

    >>> all_sets_have_relatives(s0)   # true, 16 and 14
    True
    >>> all_sets_have_relatives(s1)   # false
    False
    """
    set_of_sets = freeze(set_of_sets)
    def has_relative(set_):
        return set_ & frozenset.union(*(set_of_sets - set((set_,))))
    return all(has_relative(set) for set in set_of_sets)
墨离汐 2024-10-02 20:04:46

根据集合的分布,这可能会提供更好的性能。

def all_intersect(s):
   count = 0
   for x, a in enumerate(s):
      for y, b in enumerate(s):
         if a & b and x!=y:
            count += 1
            break
   return count == len(s)

This may give better performance depending on the distribution of the sets.

def all_intersect(s):
   count = 0
   for x, a in enumerate(s):
      for y, b in enumerate(s):
         if a & b and x!=y:
            count += 1
            break
   return count == len(s)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文