每个元素的最大重复次数的组合

Posted

技术标签:

【中文标题】每个元素的最大重复次数的组合【英文标题】:Combinations with max repetitions per element 【发布时间】:2018-02-10 13:33:44 【问题描述】:

我想获得一个 k 大小的元组列表,其中包含一个元素列表的组合(我们称之为elements),类似于itertools.combinations_with_replacement(elements, k) 所做的事情。不同之处在于我想为每个元素的替换数量添加一个最大值。

例如,如果我运行以下命令:

elements = ['a', 'b']
print(list(itertools.combinations_with_replacement(elements, 3)))

我明白了:

[('a', 'a', 'a'), ('a', 'a', 'b'), ('a', 'b', 'b'), ('b', 'b', 'b')]

我想要以下内容:

elements = 'a': 2, 'b': 3
print(list(combinations_with_max_replacement(elements, 3)))

哪个会打印

[('a', 'a', 'b'), ('a', 'b', 'b'), ('b', 'b', 'b')]

请注意,每个元组中'a' 的最大数量是2,因此('a', 'a', 'a') 不是结果的一部分。

我宁愿避免循环遍历 itertools.combinations_with_replacement(elements, k) 计算每个元组中的元素并将它们过滤掉的结果。

如果我可以提供更多信息,请告诉我。

感谢您的帮助!

更新

我试过了:

elements = ['a'] * 2 + ['b'] * 3
print(set(itertools.combinations(elements, 3)))

然后得到:

('a', 'b', 'b'), ('b', 'b', 'b'), ('a', 'a', 'b')

我得到了我需要的元素,但我失去了顺序,看起来有点老套

【问题讨论】:

使用set 本质上只是通过迭代删除重复项... 是的,这是正确的,所以我没有任何进展 我刚刚发布了一个不使用任何过滤的解决方案 【参考方案1】:

我知道您不想循环遍历结果,但以这种方式过滤输出可能更容易。

def custom_combinations(elements, max_count):
    L = list(itertools.combinations_with_replacement(elements, max_count))
    for element in elements.keys():
        L = list(filter(lambda x: x.count(element) <= elements[element], L))
    return L

【讨论】:

感谢您的回答。您的方法按预期运行,但我希望避免循环遍历元素,因为它增加了O(n^2) 额外的复杂性,n 是返回的组合数量,我预计这是一个很大的数字【参考方案2】:

纯 Python 解决方案(即没有itertools

你可以使用递归

def combos(els, l):
    if l == 1:
        return [(k,) for k, v in els.items() if v]
    cs = []
    for e in els:
        nd = k: v if k != e else v - 1 for k, v in els.items() if v
        cs += [(e,)+c for c in combos(nd, l-1)]
    return cs

测试表明它有效:

>>> combos('a': 2, 'b': 3, 3)
[('b', 'b', 'b'), ('b', 'b', 'a'), ('b', 'a', 'b'), ('b', 'a', 'a'), ('a', 'b', 'b'), ('a', 'b', 'a'), ('a', 'a', 'b')]

请注意,我们确实会取消订单,但如果我们按照您的要求将 els 作为 dictionary 传递,这是不可避免的。

【讨论】:

总有一个OrderedDict 感谢您的回答。您的方法会返回不需要的元素,例如 ('b', 'a', 'b')itertools.combinations 将其视为与 ('a', 'b', 'b') 相同,因此不会返回。此外,对它进行计时,它比通过循环过滤元素的方法要慢得多。整个想法不是增加时间复杂度。另外,如果这有助于保持顺序,我不介意使用元组列表或 OrderedDict 【参考方案3】:

我相信这种递归解决方案具有您想要的时间复杂度。

我们不是传递一个字典,而是传递一个项目对的列表。我们还传递了start_idx,它告诉“较低”递归函数调用忽略较早的元素。这解决了另一个递归答案的乱序问题。

def _combos(elements, start_idx, length):
    # ignore elements before start_idx
    for i in range(start_idx, len(elements)):
        elem, count = elements[i]
        if count == 0:
            continue
        # base case: only one element needed
        if length == 1:
            yield (elem,)
        else:
            # need more than one elem: mutate the list and recurse
            elements[i] = (elem, count - 1)
            # when we recurse, we ignore elements before this one
            # this ensures we find combinations, not permutations
            for combo in _combos(elements, i, length - 1):
                yield (elem,) + combo
            # fix the list
            elements[i] = (elem, count)


def combos(elements, length):
    elements = list(elements.items())
    return _combos(elements, 0, length)

print(list(combos('a': 2, 'b': 3, 3)))
# [('a', 'a', 'b'), ('a', 'b', 'b'), ('b', 'b', 'b')]

作为奖励,随着输入大小的增长,分析表明它比 set(itertools.combinations(_)) 解决方案的性能更高。

print(timeit.Timer("list(combos('a': 2, 'b': 2, 'c': 2, 3))",
             setup="from __main__ import combos").timeit())
# 9.647649317979813
print(timeit.Timer("set(itertools.combinations(['a'] * 2 + ['b'] * 2 + ['c'] * 2, 3))").timeit())
# 1.7750148189952597

print(timeit.Timer("list(combos('a': 4, 'b': 4, 'c': 4, 4))",
             setup="from __main__ import combos").timeit())
# 20.669851204031147
print(timeit.Timer("set(itertools.combinations(['a'] * 4 + ['b'] * 4 + ['c'] * 4, 4))").timeit())
# 28.194088937016204

print(timeit.Timer("list(combos('a': 5, 'b': 5, 'c': 5, 5))",
             setup="from __main__ import combos").timeit())
# 36.4631432640017
print(timeit.Timer("set(itertools.combinations(['a'] * 5 + ['b'] * 5 + ['c'] * 5, 5))").timeit())
# 177.29063899395987

【讨论】:

以上是关于每个元素的最大重复次数的组合的主要内容,如果未能解决你的问题,请参考以下文章

C语言,查找数组里重复出现的数字;

用java找出这几个list,所有可能的组合,并且组合结果的list中的数据不允许重复

数组中数字出现的次数

来自不同箱的元素的所有可能组合(每个箱中的一个元素)[重复]

删除重复元素并计算ArrayList中的重复次数

剑指 Offer II 082. 含有重复元素集合的组合