[英]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
位。 如果全部满足基本条件,则候选人通过验证。代码:
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 )。
解决方案如下所示:
最坏情况的运行时间为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.