归纳推理
另一种思考问题的方式不涉及范围、索引或递增它们,导致许多off-by-one errors。相反,我们可以推理问题inductively -
- 如果输入
t 为空,则生成空集
- (归纳)
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
更改顺序
我只想按照示例中的顺序处理。
您要求的特定顺序可以通过对产量重新排序来实现。我还进行了调整以从输出中删除空集-
- 如果输入
t为空,则停止
- (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',)]