【问题标题】:Python: How to find all combinations of a list when changing only specific elementsPython:仅更改特定元素时如何查找列表的所有组合
【发布时间】:2020-02-15 23:34:54
【问题描述】:

使用 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]

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

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

我一直试图找到一个解决方案,而不必使用不合理数量的 for 循环,这将适用于可能附加更多字符串的更大列表。我一直在考虑使用 itertools 但似乎找不到方法。

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

任何帮助将不胜感激,在此先感谢。

【问题讨论】:

  • 一种方法可能是将位置视为索引,获取索引的所有 3 长排列,然后通过插入字符串按需创建列表(如果需要)。
  • 字符串是否允许相互重新排序?
  • 是的,你可以重新排序字符串

标签: python list loops recursion itertools


【解决方案1】:

我猜你可以在@wjandrea 的 cmets 行中做点什么:

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]

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

【讨论】:

  • 哇,谢谢你的快速回复,这正是我想要的
  • 循环调用list.insert效率低。
  • @DanielMesejo 您可以参考我的答案以获得更有效的实施。
【解决方案2】:

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

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

为了获得更高的效率,您可以在 combinationspermutation 的两个生成器上使用 itertools.product,以避免不得不使用重复计算相同排列的嵌套循环:

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)])

【讨论】:

  • 是的,这样更快。
【解决方案3】:

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

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)
 ...
 ]

【讨论】:

    【解决方案4】:

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

    我们可以将此方法应用于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 次操作,最后进行一次复制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-07
      • 1970-01-01
      • 1970-01-01
      • 2020-10-05
      • 1970-01-01
      • 2021-01-16
      • 2013-08-26
      • 1970-01-01
      相关资源
      最近更新 更多