【问题标题】:translating a recursive solution to find all subsets of a list翻译递归解决方案以查找列表的所有子集
【发布时间】:2015-12-11 19:59:18
【问题描述】:

我想知道是否有一种相对简单的方法可以迭代地查找列表的所有子集。使用递归,这很容易完成并且非常简洁......

def partial(alist, pos, chosen):
    if pos == len(alist):
        return [chosen]
    else:
        return partial(a list, pos+1, chosen) \
               + partial(alist, pos+1, chosen + [alist[pos]])

这将返回一个列表列表,其中包含列表的所有子集。

有没有办法迭代地做这样的事情而不会过于复杂,或者递归是最好的方法?一些伪代码或解释迭代方法的东西会很有帮助。我知道itertools 很有帮助,但如果可能的话,我想要一个不需要它的解决方案。

【问题讨论】:

  • 只是为了澄清partial([a, b, c]) 创建:[[a], [b], [c], [a,b], [a,c], [b,c], [a,b,c]]?
  • @shuttle87 是的,这就是它的作用,它还包括空列表,但这不是必需的
  • 这里使用的其他参数是什么?如果唯一的任务是生成power set,那么使用 itertools 相当容易。
  • @shuttle87 其他参数是计数器和列表列表,所以你可以这样运行它partial(list, 0,[])
  • 如果是用于创建 powersets,我会看看 powerset of a given set - 它同时列出了生成器和线性 itertools 方法

标签: python algorithm recursion


【解决方案1】:

因为有 2**n 子集(n 是列表的长度)。您可以创建一个从02**(n-1) 的计数器。然后通过在计数器二进制形式中添加对应位设置为1 的元素,在每次迭代中创建一个列表(这是一个子集)。

counter = 5
binary_form = 101
you create a subset using first and third element of the original list

counter = 7
binary_form = 111
you create a subset using first, second and third element of the original list

可以这样实现,

result = [] 
A = [1,2,3,4]
for i in range(0,2**len(A)):
    binary = bin(i)[2:] 
    binary = '0'*(len(A)-len(binary)) + binary
    subset = [ A[i] for i,x in enumerate(binary) if x=='1' ]
    print binary,subset
    result.append(subset)
print result

输出

0000 []
0001 [4]
0010 [3]
0011 [3, 4]
0100 [2]
0101 [2, 4]
0110 [2, 3]
0111 [2, 3, 4]
1000 [1]
1001 [1, 4]
1010 [1, 3]
1011 [1, 3, 4]
1100 [1, 2]
1101 [1, 2, 4]
1110 [1, 2, 3]
1111 [1, 2, 3, 4]

但如 cmets 和其他答案中所述,最好不要制作二进制字符串。如果您想检查是否设置了某个位,您可以将1 移动所需的数量并使用该数字进行按位运算。例如,要检查third 位是否在423 中设置,您可以这样做:

number = 423
if number & 1<<3:
    print 'this bit is set'

【讨论】:

  • 我喜欢这里的整体思路,但我会避免在此处使用字符串进行索引。
  • 我不明白。你指的是哪一部分?
  • 这个答案stackoverflow.com/a/34188401/296460 与您的方法非常相似,但不使用任何字符串。
  • 基本上这些数字在计算机中是已经二进制的,你只需要对它们进行按位运算来提取你需要的信息。通过创建一个包含 '0' 和 '1' 的 ascii 字符的字符串,然后对其进行测试,您实际上是在创建至少 8 倍的内存使用量,因为每个 ascii 字符至少需要以 8 位存储。
  • 是的,我知道,你是对的。我只是想展示真正发生的事情并打印每次迭代的输出。但我完全同意你的看法。
【解决方案2】:

正如@sudomakeinstall2 提到的,您可以使用从02**(n-1) 的计数器来遍历列表,并将其用作掩码以从alist 中选择值。

def partial(alist):
    result = []
    for i in range(2 ** len(alist)): # iterate over all results
        tmp = []
        for j in range(len(alist)):
            if (2 ** j) & i:         # use bit mask to pick the value
                tmp.append(alist[j])
        result.append(tmp)

    return result

结果可能非常非常大,你可能想创建一个生成器来懒惰地评估。

使用列表理解和生成器:

def partial(alist):
    for i in range(2 ** len(alist)):
        yield [alist[j] for j in range(len(alist)) if 2 ** j & i]

你可以调用它

for i in partial([1, 2, 3]):
    print(i)

result = list(partial([1, 2, 3]))

【讨论】:

    【解决方案3】:

    构建子集列表本质上与构建大小为 1 到输入长度的所有组合的列表相同。所以一种解决方案是使用itertools

    import itertools
    
    def all_subsets(l):
        res = []
        for subset_len in range(1, len(l)+1):
            for combo in itertools.combinations(l, subset_len):
                res.append(combo)
        return res
    
    data = [1, 2, 3, 4]
    print(all_subsets(data))
    

    在此处查看实际操作:http://ideone.com/geTdUS

    在这种情况下,我不愿过多使用递归的主要原因是,您最终可能会遇到大量递归调用,并且 Python 中的最大递归深度可能不够深。也就是说,我不完全确定这个 itertools 解决方案适用于多大的输入大小。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-03
      • 2021-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多