如何生成计数器的所有子集?

Posted

技术标签:

【中文标题】如何生成计数器的所有子集?【英文标题】:How to generate all the subsets of a counter? 【发布时间】:2020-07-14 02:49:59 【问题描述】:

我需要编写一个名为char_counts_subsets 的函数,该函数将字符计数字典作为参数,并在考虑字符计数值的情况下返回该字典的所有子集。示例代码如下所示:

char_counts = "a": 1, "b": 2

def char_counts_subsets(cc):
    return [,
            "b": 1, "b": 2, "a": 1,
            "a": 1, "b": 1, "a": 1, "b": 2
            ] # ordering of the subsets isn't important

print(char_counts_subsets(char_counts))

我怎样才能概括这个函数,以便它可以与任何cc 字典一起使用?

【问题讨论】:

【参考方案1】:

这是一个组合问题,最好使用itertools 解决。

from itertools import product

将每个字典项展开为一系列项:

range_items = [[(x, z) for z in range(y + 1)] for x,y in char_counts.items()]
#[[('a', 0), ('a', 1)], [('b', 0), ('b', 1), ('b', 2)]]

对每个范围内的每个项目与所有其他范围内的每个项目进行笛卡尔积:

products = product(*range_items)
#[(('a', 0), ('b', 0)), (('a', 0), ('b', 1)),...(('a', 1), ('b', 2))]

消除具有 0 个计数器的对,并将剩余部分转换为具有 dict 理解的字典:

[k: v for k, v in pairs if v > 0 for pairs in products]
#[, 'b': 1, 'b': 2, 'a': 1, 'a': 1, 'b': 1, 'a': 1, 'b': 2]

【讨论】:

【参考方案2】:

我喜欢DYZ's answer,但我想知道是否有可能使它成为一个高效的迭代器。 DYZ 的range_items 的空间复杂度类似于 O(n+m),其中 n 是元素的数量,m 是它们的计数之和。我的解决方案在ranges 本身上使用product,我很确定这是 O(n)。

另外,对于术语,char_counts 基本上是一个multiset,输出与power set 非常相似,所以我猜你会称它为“power multiset”。顺便说一句,看看collections.Counter,它是标准库中的一个多集对象。

import itertools

def power_multiset(multiset):
    """
    Generate all sub-multisets of a given multiset, like a powerset.

    Output is an iterator of dicts.
    """
    elems = []
    ranges = []
    for elem, count in sorted(multiset.items()):
        elems.append(elem)
        ranges.append(range(count+1))

    for sub_counts in itertools.product(*ranges):
        # "if c" filters out items with a 0 count
        yield e: c for e, c in zip(elems, sub_counts) if c
>>> char_counts = "a": 1, "b": 2
>>> list(power_multiset(char_counts))
[, 'b': 1, 'b': 2, 'a': 1, 'a': 1, 'b': 1, 'a': 1, 'b': 2]

【讨论】:

另一方面,如果range_items 的大小实际上是一个问题,那么输出 的大小无论如何都会使整个任务完全没有希望。此外,range_items 的空间消耗仅与计数的总和成正比,而不是乘积。 @user2357112 感谢您的反馈。我编辑澄清。我想象一个一个地遍历子多重集,在这种情况下,迭代器更有效。我还修复了 big-O;是现在吗?【参考方案3】:

没有 itertools,这对我有用。可能需要稍微缩短一下,以便更好地获取密钥。这是我无需查找任何内容的最快方法。

def char_counts_subsets(cc):
    subset = []
    for key in cc:
        subset.append(key: cc[key])
        if cc[key]!= 1:
            for i in range(1, cc[key]):
                subset.append(key: i)
    subset2 = []
    for i, item in enumerate(subset):
        for key in item:
            newitem = key: item[key]
            for item2 in subset:
                for key2 in item2:
                    if key != key2:
                        newitem.update(key2: item2[key2])
                        if newitem not in subset2:
                            subset2.append(newitem)             
    subset.extend(subset2)
    return subset

【讨论】:

我怀疑您的解决方案取决于输入:由于我的示例字典长度为 2,因此您可以使用 4 个嵌套循环来解决问题。 for item in subset: for key in item: for item2 in subset: for key2 in item2: 但是,如果您向字典中添加另一个键,它将无法按预期工作。 我运行了这个,它对我有用:char_counts = "a": 1, "b": 2, "c": 3 顺序并不完美,但确实给出了所有子集 这里是结果 ['a': 1, 'b': 2, 'b': 1, 'c': 3, 'c': 1, 'c': 2, 'a': 1, 'b': 1, 'c': 2, 'b': 2, 'a': 1, 'c': 2 , 'b': 1, 'a': 1, 'c': 2, 'c': 3, 'a': 1, 'b': 1, 'c': 1, 'a ': 1, 'b': 1, 'c': 2, 'a': 1, 'b': 1] "a": 1, "b": 2, "c": 3 的结果中缺少这些:[, 'b': 1, 'c': 1, 'b': 1, 'c': 2, 'b': 1, 'c': 3, 'b': 2, 'c': 1, 'b': 2, 'c': 2, 'b': 2, 'c': 3, 'a': 1, 'c': 1, 'a': 1, 'c': 2, 'a': 1, 'c': 3, 'a': 1, 'b': 1, 'a': 1, 'b': 2, 'a': 1, 'b': 2, 'c': 1, 'a': 1, 'b': 2, 'c': 3]

以上是关于如何生成计数器的所有子集?的主要内容,如果未能解决你的问题,请参考以下文章

ABC253EX (fzt子集计数+矩阵树定理)

如何制作分类列计数子集的条形图?

使用严格函数式编程从 poset 生成 DAG

FP增长算法

如何找出为啥所有数据子集的总和比总和小1

jmeter里如何生成逐渐加一的数