Python:仅更改特定元素时如何查找列表的所有组合

时间:2019-10-19 01:45:47

标签: python list loops recursion itertools

使用python,我尝试通过仅更改列表中的特定元素来查找列表的所有组合。例如,如果我有一个列表[1,2,3,4,5,6,7,8,9]并向其添加3个字符串,那么我将拥有:

[1,2,3,4,5,6,7,8,9,'string','string','string']

然后我想查找仅允许更改字符串位置时列表可以采用的所有组合。

例如

[1,2,3,4,5,'string',6,7,8,9,'string','string']
[1,2,'string',3,4,5,'string',6,7,'string',8,9]
['string',1,'string',2,3,4,'string',5,6,7,8,9]

等,同时仍以相同的升序保留原始数字列表。 我并不一定要一次存储每个简单的组合,而只是尝试类似的事情:

  • 遍历所有可能的组合
  • 对于每种可能性,检查条件
  • 如果为true,则将列表分配给变量
  • 如果为false,则继续迭代

我一直试图找到一种解决方案,而不必使用不必要的for循环,并且该解决方案将适用于可能附加了更多字符串的较大列表。我一直在寻找使用itertools的方法,但似乎找不到方法。

我发现一个解决方案可能是只使用itertools.permutations(带有附加字符串的列表),然后使用条件检查数字是否按升序排列,但是我担心这种方法的确效率低下并且会占用会增加内存,特别是在处理较大的列表时。

我们将不胜感激,在此先感谢您。

4 个答案:

答案 0 :(得分:3)

我想您可以在@wjandrea的注释行中做些事情:

from itertools import combinations, permutations

lst = [1,2,3,4,5,6,7,8,9]

strings = ['foo', 'bar', 'foobar']

for positions in combinations(range(len(lst) + len(strings)), len(strings)):
    for permutation in permutations(strings, len(strings)):
        cop = lst[:]
        for string, pos in zip(permutation, positions):
            cop.insert(pos, string)
        print(cop)

输出(小样本)

['foo', 'foobar', 1, 2, 3, 'bar', 4, 5, 6, 7, 8, 9]
['bar', 'foo', 1, 2, 3, 'foobar', 4, 5, 6, 7, 8, 9]
['bar', 'foobar', 1, 2, 3, 'foo', 4, 5, 6, 7, 8, 9]
['foobar', 'foo', 1, 2, 3, 'bar', 4, 5, 6, 7, 8, 9]
['foobar', 'bar', 1, 2, 3, 'foo', 4, 5, 6, 7, 8, 9]
['foo', 'bar', 1, 2, 3, 4, 'foobar', 5, 6, 7, 8, 9]
['foo', 'foobar', 1, 2, 3, 4, 'bar', 5, 6, 7, 8, 9]

请注意,此解决方案假定您可以重新排序字符串。

答案 1 :(得分:2)

@DanielMesejo的答案有效,但使用list.insert方法将每个字符串插入其在数字列表副本中的位置,由于list.insert的平均时间复杂度为 O,因此效率低下(n)每次迭代。

根据位置和字符串排列的组合来构造每个列表的更有效方法是使用dict,将位置映射到字符串,然后在整个长度上迭代该位置,如果该位置在dict,在该位置输出字符串;否则,将使用迭代器输出列表中的下一个数字。

为了获得更高的效率,您可以在itertools.productcombinations的两个生成器上使用permutation来避免使用嵌套循环来重复计算相同置换的麻烦:

from itertools import combinations, permutations, product

lst = [1,2,3,4,5,6,7,8,9]
strings = ['foo', 'bar', 'foobar']

total_len = len(lst) + len(strings)
for positions, permutation in product(combinations(range(total_len), len(strings)),
                                      permutations(strings, len(strings))):
    string_pos = dict(zip(positions, permutation))
    numbers = iter(lst)
    print([string_pos[i] if i in string_pos else next(numbers) for i in range(total_len)])

答案 2 :(得分:1)

您可以将递归与生成器一起使用:

data, s = [1,2,3,4,5,6,7,8,9], ['foo', 'bar', 'foobar']
def groups(d):
   if all(i in d for i in s):
     yield d
   else:
     for i in range(len(d)):
       for k in filter(lambda x:x not in d, s):
          yield from groups(d[:i]+[k]+d[i:])

result = [*set(map(tuple, groups(data)))]

输出:

[(1, 2, 3, 4, 'foo', 'foobar', 5, 'bar', 6, 7, 8, 9), 
 (1, 'foo', 'foobar', 2, 'bar', 3, 4, 5, 6, 7, 8, 9), 
 (1, 'foobar', 2, 3, 'bar', 4, 5, 6, 'foo', 7, 8, 9), 
 ('bar', 'foo', 1, 2, 'foobar', 3, 4, 5, 6, 7, 8, 9), 
 (1, 2, 'foobar', 3, 4, 5, 6, 'bar', 'foo', 7, 8, 9), 
 (1, 2, 3, 'foo', 'foobar', 4, 5, 6, 'bar', 7, 8, 9), 
 (1, 'bar', 2, 3, 'foo', 4, 5, 6, 7, 'foobar', 8, 9), 
 (1, 2, 3, 'foobar', 4, 5, 'bar', 6, 7, 'foo', 8, 9), 
 (1, 2, 3, 'foo', 4, 5, 6, 'foobar', 'bar', 7, 8, 9), 
 (1, 'foobar', 2, 3, 4, 'bar', 5, 'foo', 6, 7, 8, 9)
 ...
 ]

答案 3 :(得分:1)

您可以通过回溯来做到这一点。在路径的每个步骤中,我们都可以附加数字或字符串(因此,路径是一系列决策)。如果到达路径的末尾,则将该路径的副本附加到结果中。

我们可以将此方法应用于strings列表的所有排列,以获得最终答案:

from itertools import permutations

def combinations(nums, strings):
    res = []

    def generate(i, j, string_combo, path):
        if i == len(nums) and j == len(strings):  # path ends
            res.append(path[:])
            return
        if i < len(nums):  # choose number
            path.append(nums[i])
            generate(i + 1, j, string_combo, path)
            path.pop()
        if j < len(strings):  # choose string
            path.append(string_combo[j])
            generate(i, j + 1, string_combo, path)
            path.pop()

    for p in permutations(strings):
        generate(0, 0, p, [])
    return res

result = combinations([1, 2, 3, 4, 5, 6, 7, 8, 9], ['A', 'B', 'C'])
print(len(result))  # 1320

此方法的时间复杂度为 O(s!(n + s)2 (n + s)。这是因为字符串列表存在 s!个排列,并且对于每个排列,我们都会回溯。每个路径的长度为(n + s),我们每步执行2次操作,最后进行一次复制。