我想获得一个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
答案 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