递归过程中的yield

9

假设我有一个Python列表,表示某些变量的范围:

conditions = [['i', (1, 5)], ['j', (1, 2)]]

这表示变量i的范围从1到5,循环内部变量j的范围为1到2。我想要一个包含每种可能组合的字典:

{'i': 1, 'j': 1}
{'i': 1, 'j': 2}
{'i': 2, 'j': 1}
{'i': 2, 'j': 2}
{'i': 3, 'j': 1}
{'i': 3, 'j': 2}
{'i': 4, 'j': 1}
{'i': 4, 'j': 2}
{'i': 5, 'j': 1}
{'i': 5, 'j': 2}

原因是我想遍历它们。但由于整个空间太大,我不想生成它们所有的内容,然后再存储并遍历那个字典列表。我考虑使用以下递归过程,但我需要在yield部分帮助。它应该在哪里?如何避免嵌套生成器?
理由是我想对它们进行迭代。但是由于整个空间太大,我不想生成所有的结果,然后再将它们存储并迭代整个字典列表。我考虑使用以下递归过程,但我需要一些关于yield部分的帮助,它应该放在哪里?如何避免嵌套生成器?
def iteration(conditions, currentCondition, valuedIndices):
    if currentCondition == len(conditions):
        yield valuedIndices
    else:
        cond = conditions[currentCondition]
        index = cond[0]
        lim1 = cond[1][0]
        lim2 = cond[1][1]
        for ix in range(lim1, lim2 + 1):
            valuedIndices[index] = ix
            yield iteration(conditions, currentCondition + 1, valuedIndices)


现在我希望能够做到以下事情:
for valued_indices in iteration(conditions, 0, {}):
    ...


3
只需要在你的函数最后一行将yield替换成yield from即可,不改变原意,让语言更加通俗易懂。 - jfaccioni
3个回答

6

这是一个情况,可能更容易退后一步重新开始。

让我们首先使用一个众所周知的技巧(涉及zip),将键和间隔分开。

>>> keys, intervals = list(zip(*conditions))
>>> keys
('i', 'j')
>>> intervals
((1, 5), (1, 2))

这两者之间的对应关系保留了原始配对;对于所有的iintervals[i]是变量keys[i]的区间。

现在,让我们从这些区间中创建适当的范围对象。

>>> intervals = [range(x, y+1) for x, y in intervals]
>>> list(intervals[0])
[1, 2, 3, 4, 5]
>>> list(intervals[1])
[1, 2]

我们可以计算这些range对象的乘积。
>>> for v in product(*intervals):
...   print(v)
...
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
(4, 1)
(4, 2)
(5, 1)
(5, 2)

您应该将其识别为每个字典所使用的值。您可以将这些值与键一起压缩,以创建适当的参数集合,用于dict命令。例如:

>>> dict(zip(keys, (1,1)))
{'i': 1, 'j': 1}

将所有内容综合起来,我们可以迭代产品以逐个生成每个dict,并将其作为一次性结果。

def iteration(conditions):
    keys, intervals = zip(*conditions)
    intervals = [range(x, y+1) for x, y in intervals]
    yield from (dict(zip(keys, v)) for v in product(*intervals))

0

你可以使用内部生成器推导式和 yield from 来简化代码:

def dict_factory(i, j):
    r1 = range(1, i + 1)
    r2 = range(1, j + 1)
    dictgen = ({'i':x, 'j':y} for x in r1 for y in r2)
    yield from dictgen

用法:

foo = dict_factory(5, 2)
while True:
    print(next(foo))

给定条件作为列表传递,我怀疑OP需要一个适用于N个条件的解决方案,而不是硬编码为2。 - Chris Doyle
@Chris Doyle 说得好,这个函数可能应该被构造成dict_factory(*args)的形式... 嗯... - neutrino_logic

0

我不确定您是否需要使用递归,但是您可以使用itertools product方法生成所有组合。这样编写的代码可以满足1到n个条件,并且仍然可以按项返回结果。

from itertools import product


def iterations2(conditions):
    labels, ranges = list(zip(*conditions))
    ranges = [range(item[0], item[1] + 1) for item in ranges]
    for nums in product(*ranges):
        yield dict(zip(labels, nums))


conditions = [['i', (1, 5)], ['j', (1, 2)], ['z', (3, 6)]]
for valued_indices in iterations2(conditions):
    print(valued_indices)

输出

{'i': 1, 'j': 1, 'z': 3}
{'i': 1, 'j': 1, 'z': 4}
{'i': 1, 'j': 1, 'z': 5}
{'i': 1, 'j': 1, 'z': 6}
{'i': 1, 'j': 2, 'z': 3}
{'i': 1, 'j': 2, 'z': 4}
{'i': 1, 'j': 2, 'z': 5}
{'i': 1, 'j': 2, 'z': 6}
{'i': 2, 'j': 1, 'z': 3}
{'i': 2, 'j': 1, 'z': 4}
{'i': 2, 'j': 1, 'z': 5}
{'i': 2, 'j': 1, 'z': 6}
{'i': 2, 'j': 2, 'z': 3}
{'i': 2, 'j': 2, 'z': 4}
{'i': 2, 'j': 2, 'z': 5}
{'i': 2, 'j': 2, 'z': 6}
{'i': 3, 'j': 1, 'z': 3}
{'i': 3, 'j': 1, 'z': 4}
{'i': 3, 'j': 1, 'z': 5}
{'i': 3, 'j': 1, 'z': 6}
{'i': 3, 'j': 2, 'z': 3}
{'i': 3, 'j': 2, 'z': 4}
{'i': 3, 'j': 2, 'z': 5}
{'i': 3, 'j': 2, 'z': 6}
{'i': 4, 'j': 1, 'z': 3}
{'i': 4, 'j': 1, 'z': 4}
{'i': 4, 'j': 1, 'z': 5}
{'i': 4, 'j': 1, 'z': 6}
{'i': 4, 'j': 2, 'z': 3}
{'i': 4, 'j': 2, 'z': 4}
{'i': 4, 'j': 2, 'z': 5}
{'i': 4, 'j': 2, 'z': 6}
{'i': 5, 'j': 1, 'z': 3}
{'i': 5, 'j': 1, 'z': 4}
{'i': 5, 'j': 1, 'z': 5}
{'i': 5, 'j': 1, 'z': 6}
{'i': 5, 'j': 2, 'z': 3}
{'i': 5, 'j': 2, 'z': 4}
{'i': 5, 'j': 2, 'z': 5}
{'i': 5, 'j': 2, 'z': 6}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接