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

Python 每个元素具有最大重复次数的组合,python,itertools,Python,Itertools,我想得到一个k大小的元组列表,其中包含元素列表的组合(我们称之为elements),类似于itertools。不同之处在于,我希望为每个元素添加最大替换次数 例如,如果我运行以下命令: elements = ['a', 'b'] print(list(itertools.combinations_with_replacement(elements, 3))) elements = {'a': 2, 'b': 3} print(list(combinations_with_max_replace

我想得到一个k大小的元组列表,其中包含元素列表的组合(我们称之为
elements
),类似于
itertools。不同之处在于,我希望为每个元素添加最大替换次数

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

elements = ['a', 'b']
print(list(itertools.combinations_with_replacement(elements, 3)))
elements = {'a': 2, 'b': 3}
print(list(combinations_with_max_replacement(elements, 3)))
我得到:

[('a', 'a', 'a'), ('a', 'a', 'b'), ('a', 'b', 'b'), ('b', 'b', 'b')]
我想要以下的东西:

elements = ['a', 'b']
print(list(itertools.combinations_with_replacement(elements, 3)))
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.compositions\u和\u replacement(elements,k)
计算每个元组中的元素并将其过滤掉

如果我能提供更多信息,请告诉我

谢谢你的帮助

更新

我试过:

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

{('a', 'b', 'b'), ('b', 'b', 'b'), ('a', 'a', 'b')}

我得到了我需要的元素,但我失去了顺序,似乎有点像黑客一样

纯Python解决方案(即没有
itertools

您可以使用递归:

测试表明它是有效的:

>>> 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
作为
字典传递,这是不可避免的。

我知道您不想循环查看结果,但这样过滤输出可能更容易

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
def自定义组合(元素、最大计数):
L=列表(itertools.组合与替换(元素,最大计数))
对于elements.keys()中的元素:

L=list(filter)(lambda x:x.count(element)我相信这个递归解决方案具有您想要的时间复杂性

我们不传递dict,而是传递项对的列表。我们还传递
start\u idx
,它告诉“lower”递归函数调用忽略前面的元素。这修复了另一个递归答案的无序问题

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.combines())解决方案更高

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

使用
set
本质上就是通过迭代来删除重复项…是的,这是正确的,因此我的结尾没有任何进展我刚刚发布了一个不使用任何过滤器的解决方案这里总是感谢您的回答。您的方法返回了不需要的元素,如
('b','a','b'))
其中的
itertools.compositions
被认为与
('a','b','b')相同
,因此没有返回。此外,计时,这比通过循环过滤元素的方法慢得多。整个想法不是为了增加时间复杂性。此外,我不介意使用元组列表或OrderedDict,如果这有助于保留顺序谢谢你的回答。你的方法的行为与预期一样,但我不介意希望避免在元素中循环,因为它增加了一个
O(n^2)
额外的复杂性,其中
n
是返回的组合数,我希望这是一个大数字