繁体   English   中英

从其他集合中查找至少包含一个元素的集合

[英]Find sets that contain at least one element from other sets

假设我们给定了n 个集合,并且想要构造所有最小集合,这些集合至少有一个与每个输入集合相同的元素。 如果不存在作为S的子集的可接受集合S' ,则集合S称为最小集合。

一个例子:

In: s1 = {1, 2, 3}; s2 = {3, 4, 5}; s3 = {5, 6}

Out: [{1, 4, 6}, {1, 5}, {2, 4, 6}, {2, 5}, {3, 5}, {3, 6}]

我的想法是将一组接一组地迭代添加到解决方案中:

result = f(s1, f(s2, f(s3, ...)))

其中f是一个合并 function ,它看起来如下:

function f(newSet, setOfSets):
   Step 1: 
      return all elements of setOfSets that share an element with newSet

   Step 2: 
      for each remaining element setE of setOfSets:
         for each element e of newSet:
            return union(setE, {e})

上述方法的问题在于,第 2 步中计算的笛卡尔积可能包含第 1 步中返回的集合的超集。我正在考虑遍历所有已经返回的集合(请参阅查找覆盖给定集合的子集的最小集合),但是这似乎太复杂且效率低下,我希望在我的特殊情况下有更好的解决方案。

如果不在步骤 2 中确定完整的笛卡尔积,我如何实现目标?

注意, 这道题和finding the smallest set only 的问题有关,但是我需要按照上面指定的方式找到所有最小的set。 我知道解决方案的数量不会是多项式的。

输入集的数量n将是几个 hundret,但这些集仅包含来自有限范围的元素(例如,大约 20 个不同的值),这也限制了集的大小。 如果算法在O(n^2)中运行是可以接受的,但它应该基本上是 output 集合的线性(可能带有对数乘数)。

由于您的空间非常有限——只有 20 个值可供选择——用一个钝器把这个东西打死:

  1. 将每个目标集(要覆盖的目标集)转换为位图。 在您给定的情况下,这将对应于 20 位的 integer,20 个值中的每个值对应一个位 position。
  2. 创建候选覆盖位图列表,整数 0 到 (2^20-1)
  3. 按顺序取整数。 使用位操作来判断每个目标集合是否有一个与候选共同的1位。 如果全部满足基本条件,则候选人通过验证。
  4. 当您验证候选者时,从候选者列表中删除所有超集整数。
  5. 当您用完候选人时,您的验证候选人就是所需的集合。 在下面的代码中,我简单地打印每个被识别的。

代码:

from time import time
start = time()

s1 = {1, 2, 3}
s2 = {3, 4, 5}
s3 = {5, 6}

# Convert each set to its bit-map
point_set = [7, 28, 48]

# make list of all possible covering bitmaps
cover = list(range(2**20))

while cover:
    # Pop any item from remaining covering sets
    candidate = cover.pop(0)
    # Does this bitmap have a bit in common with each target set?
    if all((candidate & point) for point in point_set):
        print(candidate)

        # Remove all candidates that are supersets of the successful covering one.
        superset = set([other for other in cover if (candidate & ~other) == 0])
        cover = [item for item in cover if item not in superset]
        print(time() - start, "lag time")

print(time() - start, "seconds")

Output -- 我没有将候选整数转换回它们的组成元素。 这是一项简单的任务。

请注意,此示例中的大部分时间都花在穷尽不是已验证覆盖集超集的整数列表,例如 32 的所有倍数(低 6 位全为零,因此与任何覆盖集都不相交).

这 33 秒是在我老化的台式电脑上; 您的笔记本电脑或其他平台几乎肯定更快。 我相信来自更高效算法的任何改进都很容易被抵消,因为该算法可以快速实现并且更容易理解。

17
0.4029195308685303 lag time
18
0.6517734527587891 lag time
20
0.8456630706787109 lag time
36
1.0555419921875 lag time
41
1.2604553699493408 lag time
42
1.381387710571289 lag time
33.005757570266724 seconds

我提出了一个基于 trie 数据结构的解决方案,如此所述。 尝试可以相对快速地确定其中一个存储集是否是另一个给定集的子集 ( Savnik, 2013 )。

解决方案如下所示:

  • 创建一个特里
  • 遍历给定的集合
    • 在每次迭代中,go 通过 trie 中的集合并检查它们是否与新集合不相交。
    • 如果是,继续; 如果不是,则将相应的新集合添加到 trie 中,除非它们是 trie 中集合的超集。

最坏情况的运行时间为O(nmc) ,如果我们仅考虑输入集的n' <= n ,则m是解的最大数量,而c是子集查找的时间因子。

代码如下。 我已经实现了基于 python package datrie的算法,它是 trie 的高效 C 实现的包装器。 下面的代码在 cython 中,但可以通过删除/交换 cython 特定命令轻松转换为纯 python。

扩展的 trie 实现:

from datrie cimport BaseTrie, BaseState, BaseIterator

cdef bint has_subset_c(BaseTrie trie, BaseState trieState, str setarr, 
                        int index, int size):
    cdef BaseState trieState2 = BaseState(trie)
    cdef int i
    trieState.copy_to(trieState2)
    for i in range(index, size):
        if trieState2.walk(setarr[i]):
            if trieState2.is_terminal() or has_subset_c(trie, trieState2, setarr, 
                                                        i, size): 
                return True
            trieState.copy_to(trieState2)
    return False


cdef class SetTrie():
    def __init__(self, alphabet, initSet=[]):
        if not hasattr(alphabet, "__iter__"):
            alphabet = range(alphabet)
        self.trie = BaseTrie("".join(chr(i) for i in alphabet))
        self.touched = False
        for i in initSet:
            self.trie[chr(i)] = 0
            if not self.touched:
                self.touched = True

    def has_subset(self, superset):
        cdef BaseState trieState = BaseState(self.trie)
        setarr = "".join(chr(i) for i in superset)
        return bool(has_subset_c(self.trie, trieState, setarr, 0, len(setarr)))

    def extend(self, sets):
        for s in sets:
            self.trie["".join(chr(i) for i in s)] = 0
            if not self.touched:
                self.touched = True

    def delete_supersets(self):
        cdef str elem 
        cdef BaseState trieState = BaseState(self.trie)
        cdef BaseIterator trieIter = BaseIterator(BaseState(self.trie))
        if trieIter.next():
            elem = trieIter.key()
            while trieIter.next():
                self.trie._delitem(elem)
                if not has_subset_c(self.trie, trieState, elem, 0, len(elem)):
                    self.trie._setitem(elem, 0)
                elem = trieIter.key()
            if has_subset_c(self.trie, trieState, elem, 0, len(elem)):
                val = self.trie.pop(elem)
                if not has_subset_c(self.trie, trieState, elem, 0, len(elem)):
                    self.trie._setitem(elem, val)


    def update_by_settrie(self, SetTrie setTrie, maxSize=inf, initialize=True):
        cdef BaseIterator trieIter = BaseIterator(BaseState(setTrie.trie))
        cdef str s
        if initialize and not self.touched and trieIter.next():
            for s in trieIter.key():
                self.trie._setitem(s, 0)
            self.touched = True

        while trieIter.next():
            self.update(set(trieIter.key()), maxSize, True)

    def update(self, otherSet, maxSize=inf, isStrSet=False):
        if not isStrSet:
            otherSet = set(chr(i) for i in otherSet)
        cdef str subset, newSubset, elem
        cdef list disjointList = []
        cdef BaseTrie trie = self.trie
        cdef int l
        cdef BaseIterator trieIter = BaseIterator(BaseState(self.trie))
        if trieIter.next():
            subset = trieIter.key()
            while trieIter.next():
                if otherSet.isdisjoint(subset):
                    disjointList.append(subset)
                    trie._delitem(subset)
                subset = trieIter.key()
            if otherSet.isdisjoint(subset):
                disjointList.append(subset)
                trie._delitem(subset)

        cdef BaseState trieState = BaseState(self.trie)
        for subset in disjointList:
            l = len(subset)
            if l < maxSize:
                if l+1 > self.maxSizeBound:
                    self.maxSizeBound = l+1
                for elem in otherSet:
                    newSubset = subset + elem
                    trieState.rewind()
                    if not has_subset_c(self.trie, trieState, newSubset, 0, 
                                        len(newSubset)):
                        trie[newSubset] = 0

    def get_frozensets(self):
        return (frozenset(ord(t) for t in subset) for subset in self.trie)

    def clear(self):
        self.touched = False
        self.trie.clear()

    def prune(self, maxSize):
        cdef bint changed = False
        cdef BaseIterator trieIter 
        cdef str k
        if self.maxSizeBound > maxSize:
            self.maxSizeBound = maxSize
            trieIter = BaseIterator(BaseState(self.trie))
            k = ''
            while trieIter.next():
                if len(k) > maxSize:
                    self.trie._delitem(k)
                    changed = True
                k = trieIter.key()
            if len(k) > maxSize:
                self.trie._delitem(k)
                changed = True
        return changed

    def __nonzero__(self):
        return self.touched

    def __repr__(self):
        return str([set(ord(t) for t in subset) for subset in self.trie])

这可以按如下方式使用:

def cover_sets(sets):
    strie = SetTrie(range(10), *([i] for i in sets[0]))
    for s in sets[1:]:
        strie.update(s)
    return strie.get_frozensets()

定时:

from timeit import timeit
s1 = {1, 2, 3}
s2 = {3, 4, 5}
s3 = {5, 6}
%timeit cover_sets([s1, s2, s3])

结果:

37.8 µs ± 2.97 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

请注意,上面的 trie 实现仅适用于大于(且不等于) 0的键。 否则,integer 到字符的映射将无法正常工作。 这个问题可以通过索引移位来解决。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM