快速找到所有子集

时间:2018-08-01 14:34:55

标签: python

我有个大字典,里面有Frozenset键。我需要找到所有给定键的子集。我看到了明显的方法:

dictionary = {
    frozenset([1]): 1,
    frozenset([2]): 2,
    frozenset([3]): 3,
    frozenset([3, 4]): 34
}
biglist= [3, 4, 5]
results = {k: v for k, v in dictionary.items() if k.issubset(biglist)}
assert results == {frozenset([3]): 3, frozenset([3, 4]): 34}

但是对于数百万个键而言,它非常慢。问题是:这种快速搜索有什么结构吗?

UPD:基本上,我不想遍历每个键上执行issubset的所有键。相反,我可以从biglist生成所有可能的集合,并检查它是否在字典中:

results = {}
maxkey = max(dictionary, key=len)
maxlen = len(dictionary[maxkey])
for lenght in range(1, maxlen):
    for subset in itertools.combinations(biglist, lenght):
        key = frozenset(subset)
        if key in dictionary:
            results[key] = dictionary[key]

但是对于长名单来说,这种方法也非常昂贵。

4 个答案:

答案 0 :(得分:2)

根据字典的大小和键的长度,既不检查dict中的所有键,也不列举所有子集并检查它们是一个很好的解决方案。取而代之的是,您可以将“扁平”字典重组为类似Trie, or Prefix Tree的形式。在这里,集合中的每个元素都将指向树的另一个分支和/或实际值:

dictionary = {
    frozenset([1]): 1,
    frozenset([2]): 2,
    frozenset([3]): 3,
    frozenset([3, 4]): 34
}

def totree(d):
    tree = {}
    for key in d:
        t = tree
        for x in sorted(key):
            t = t.setdefault(x, {})
        t["value"] = d[key]
    return tree

tree = totree(dictionary)
# {1: {'value': 1}, 2: {'value': 2}, 3: {'value': 3, 4: {'value': 34}}}

现在,您可以递归检查这些树,并yield每个具有值的键。除了枚举所有子集,这只会扩展到目前为止所有元素都在树中的那些分支。

def check_subsets(tree, key, prefix=[]):
    if "value" in tree:
        yield prefix, tree["value"]
    for i, x in enumerate(key):
        if x in tree:
            yield from check_subsets(tree[x], key[i+1:], prefix+[x])

biglist= [3, 4, 5]
res = list(check_subsets(tree, sorted(biglist)))
# [([3], 3), ([3, 4], 34)]

请注意,必须按排序顺序添加/检查树中的键和查找键,这很重要,否则可能会丢失相关的子树。

附录1:此应该应该清楚,但要确保:当然,如果您为每次查找重新构建树,则这种方法将无济于事,否则您也可以线性地进行扫描所有按键。取而代之的是,您必须创建一次树,然后将其重新用于多次查找,并可能使用添加到集合中的新元素进行更新。

附录2:您当然可以使用任何键代替前缀树中该节点上的实际值来代替"value"。您可以使用None,也可以使用很长的字符串或较大的随机数,以确保不会成为任何键集中的元素。只需对totreecheck_subtree函数进行一些修改,您还可以定义一个自定义Tree类...

class Tree:
    def __init__(self, value=None, children=None):
        self.value = value
        self.children = children or {}
    def __repr__(self):
        return "Tree(%r, %r)" % (self.value, self.children)

...但是恕我直言,只使用带有一些特殊值键的嵌套字典会更简单。

答案 1 :(得分:1)

这个答案大概是基于前缀树的思想,但是这个问题来自一个稍有不同的技巧。基本上,我们想弄清楚在使用某种提前停止枚举所有子集时如何避免触及整个搜索空间。

如果我们将数据安排到“ SubsetTree”中,从而使节点的所有子节点都成为该节点的超集,则只要到达非当前查询子集的节点,我们就可以停止探索树所有的孩子也不是子集。当我们构建树时,我们希望长父母而不是短父母,因为这会增加我们搜索中的早期停止量。

如果将所有这些放在一起,它看起来像这样:

class SubsetTree:
    def __init__(self, key):
        self.key = key
        self.children = []

    def longestSubset(self, query):
        if not self.key.issubset(query):
            return None
        more = (x.longestSubset(query) for x in self.children)
        more = filter(lambda i: i is not None, more)
        return max(more, key=lambda x: len(x.key), default=self)

    def allSubsets(self, query):
        if not self.key.issubset(query):
            return
        if len(self.key) > 0:
            yield self.key
        for c in self.children:
            yield from c.allSubsets(query)


def buildSubtree(sets):
    sets = sorted(sets, key=lambda x: len(x))
    tree = SubsetTree(frozenset())
    for s in sets:
        node = SubsetTree(s)
        tree.longestSubset(s).children.append(node)
    return tree

dictionary = {
    frozenset([1]): 1,
    frozenset([2]): 2,
    frozenset([3]): 3,
    frozenset([3, 4]): 34
}
biglist= [3, 4, 5]

subsetTree = buildSubtree(dictionary.keys())
allSubsets = subsetTree.allSubsets(set(biglist))
results = {k: dictionary[k] for k in allSubsets}
assert results == {frozenset([3]): 3, frozenset([3, 4]): 34}

答案 2 :(得分:0)

如何将Frozenset密钥和给定集合都编码为二进制代码?

然后您可以对冻结集密钥和给定集合的代码进行位和运算,如果结果等于冻结集密钥的二进制代码,则该密钥为给定集合的子集。

通过这种方式预先计算给定的集合,我认为它将使速度更快。


在这种情况下:

dictionary = {
    frozenset([1]): 1, # a
    frozenset([2]): 2, # b
    frozenset([3]): 3, # c
    frozenset([3, 4]): 34 # d
}
biglist= [3, 4, 5] # z

a: 10000
b: 01000
c: 00100
d: 00110
z: 00111

a & z = 00000 != a = 10000, no
b & z = 00000 != b = 01000, no
c & z = 00100 == c = 00100, yes
d & z = 00110 == d = 00110, yes

答案 3 :(得分:0)

这里有点通用,我认为您可以使用名为Bloom Filter的数据结构来快速丢弃绝对不在集合中的数据。稍后,您可以对可能的候选对象进行简单扫描,希望最后一步只是您的集合的一小部分。

以下是布隆过滤器的Python实现:https://github.com/axiak/pybloomfiltermmap

引用他们的示例:

>>> fruit = pybloomfilter.BloomFilter(100000, 0.1, '/tmp/words.bloom')
>>> fruit.update(('apple', 'pear', 'orange', 'apple'))
>>> len(fruit)
3
>>> 'mike' in fruit
False
>>> 'apple' in fruit
True