【问题标题】:recursive sequentially powerset function in pythonpython中的递归顺序powerset函数
【发布时间】:2022-01-24 14:56:49
【问题描述】:

我对 [1,2,3] 的期望 ->

[1]
[1, 2]
[1, 2, 3]
[1, 3]
[2]
[2, 3]
[3]

但是我的函数给出了这个结果,我无法修复它 ->

def foo(L,first,last,Output):
    if first>=last:
        return
    for i in range(first ,last):
        print(Output+[L[i]])
        foo(L,first+1,last,Output+[L[i]])
        


foo([1,2,3],0,3,[])


[1]
[1, 2]
[1, 2, 3]
[1, 3]
[1, 3, 3]
[2]
[2, 2]
[2, 2, 3]
[2, 3]
[2, 3, 3]
[3]
[3, 2]
[3, 2, 3]
[3, 3]
[3, 3, 3]

在某些情况下,我想停止计算并继续进行其他计算:

假设1和2走到一起,不要再继续了

-> 用于 [1,2,3] 和 (1,2)

我的期望:

[1]
[1, 3]
[2]
[2, 3]
[3]

迭代函数对我也有好处

【问题讨论】:

  • 让我们考虑foo([1, 2, 3], 0, 3, [])[1, 2, 3] 的第一个元素是 1。每个子集可以包含 1,也可以不包含 1。因此,您要收集 foo([1, 2, 3], 1, 3, []) 的结果,它们是不包含 1 的子集,以及 foo([1, 2, 3], 1, 3, [1]) 的结果,它们是包含的子集1.所以你应该有两个递归调用。
  • PS 我强烈建议使用return 关键字,并避免使用print。虽然,您可以保留一些 print 用于调试目的,但在完成后将其删除。应使用return 返回实际返回值(子集列表)。

标签: python recursion


【解决方案1】:

归纳推理

另一种思考问题的方式不涉及范围、索引或递增它们,导致许多off-by-one errors。相反,我们可以推理问题inductively -

  1. 如果输入 t 为空,则生成空集
  2. (归纳)t 至少有一个元素。对于递归子问题powerset(t[1:]) 中的所有p,yield p 和yield p 加上第一个元素t[0]
def powerset(t):
  if not t:
    yield ()                       # 1. empty t
  else:
    for p in powerset(t[1:]):      # 2. at least one element
      yield p
      yield (t[0], *p)

通过使用yield,我们将所需的效果移出 powerset 函数。这允许调用者决定每个生成的集合会发生什么 -

for p in powerset("abc"):
  print(p)                   # <- desired effect
()
('a',)
('b',)
('a', 'b')
('c',)
('a', 'c')
('b', 'c')
('a', 'b', 'c')
for p in powerset("abc"):
  print("".join(p))     # <- different effect
a
b
ab
c
ac
bc
abc

更改顺序

我只想按照示例中的顺序处理

您要求的特定顺序可以通过对产量重新排序来实现。我还进行了调整以从输出中删除空集-

  1. 如果输入t为空,则停止
  2. (inductive) t 至少有一个元素
    • 产生第一个元素的单例集,t[0]
    • 将第一个元素添加到子问题powerset(t[1:]) 和yield 的每个结果中
    • 产生子问题powerset(t[1:])的每个结果
def powerset(t):
  if not t:
    return                 # 1.
  else:
    yield (t[0],)          # 2.
    yield from map(lambda p: (t[0], *p), powerset(t[1:]))
    yield from powerset(t[1:])

注意上面我们计算powerset(t[1:]) 两次。这是一种浪费,可以使用itertools.tee 来避免 -

from itertools import tee

def powerset(t):
  if not t: return
  yield (t[0],)
  left, right = tee(powerset(t[1:]))          # <- tee left & right
  yield from map(lambda p: (t[0], *p), left)  # <- left
  yield from right                            # <- right
for p in powerset("abc"):
  print(p)
('a',)
('a', 'b')
('a', 'b', 'c')
('a', 'c')
('b',)
('b', 'c')
('c',)

所有子集的列表

不使用yield可以做到吗?我需要将其保存在全局列表中

Python 在其标准库中使用可迭代对象。规定的方法是使用yield,但是使用list很容易转换为列表-

result = list(powerset("abc"))
print(result)
[('a',), ('a', 'b'), ('a', 'b', 'c'), ('a', 'c'), ('b',), ('b', 'c'), ('c',)]

不使用yield

如果你有一些令人信服的理由powerset必须返回一个数组而不是一个可迭代的,那么转换是基本的。注意程序的结构是相同的 -

def powerset(t):
  if not t: return []
  result = list(powerset(t[1:]))
  return [
    (t[0],),
    *map(lambda p: (t[0], *p), result),
    *result
  ]
print(powerset("abc"))
[('a',), ('a', 'b'), ('a', 'b', 'c'), ('a', 'c'), ('b',), ('b', 'c'), ('c',)]

【讨论】:

  • () ('a',) ('a', 'b') ('a', 'b', 'c') 必须是连续的
  • @XLVII 有什么问题?
  • 有点复杂,是关于组合学的。我只想按照示例中所示的顺序处理。
  • @XLVII 我没有意识到具体的顺序很重要。我对帖子进行了更新。如果您有任何问题,请告诉我^_^
  • 不使用yield可以做到吗?我不完全理解算法,我需要把它放在一个全局列表中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-06
  • 2011-04-25
  • 1970-01-01
  • 2012-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多