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

时间:2018-02-10 13:33:45

标签: python itertools

我想获得一个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')}

我得到了我需要的元素,但是我失去了顺序,似乎有些hacky

3 个答案:

答案 0 :(得分:0)

纯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传递,则这是不可避免的。

答案 1 :(得分:0)

我知道你不想循环结果,但也许以这种方式过滤输出更容易。

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

答案 2 :(得分:0)

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

我们传递一个项目对的列表,而不是传递一个字典。我们还传递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