`yield 'dentro de um procedimento recursivo

8

Digamos que eu tenho uma lista Python representando intervalos para algumas variáveis:

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

Isso representa que a variável ivaria de 1 a 5 e, dentro desse loop, a variável jvaria de 1 a 2. Quero um dicionário para cada combinação possível:

{'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}

A razão é que eu quero iterar sobre eles. Mas como todo o espaço é muito grande, não quero gerar todos eles, armazená-los e iterar sobre essa lista de dicionários. Pensei em usar o seguinte procedimento recursivo, mas preciso de ajuda com a yieldparte. Onde deveria estar? Como evito geradores aninhados?

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)

Agora eu gostaria de poder fazer:

for valued_indices in iteration(conditions, 0, {}):
    ...
Noel Arteche
fonte
3
Simplesmente substitua yieldpor yield fromna última linha da sua função.
jfaccioni 03/03

Respostas:

5

É um caso em que pode ser mais fácil dar um passo atrás e começar de novo.

Vamos começar separando as teclas e os intervalos, usando um truque conhecido que envolve zip:

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

(A correspondência entre os dois preserva o emparelhamento original; intervals[i]é o intervalo da variável keys[i]para todos i.)

Agora, vamos criar objetos de intervalo adequados a partir desses intervalos

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

Podemos calcular o produto desses rangeobjetos

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

que você deve reconhecer como os valores a serem usados ​​para cada ditado. Você pode compactar cada um desses valores com as chaves para criar um conjunto apropriado de argumentos para o dictcomando. Por exemplo:

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

Juntando tudo isso, podemos iterar o produto para produzir cada um dict, produzindo-o.

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))
chepner
fonte
0

Talvez você possa simplificar um pouco com uma compreensão interna do gerador e 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

use como:

foo = dict_factory(5, 2)
while True:
    print(next(foo))
neutrino_logic
fonte
Dadas condições são passadas como lista de listas, suspeito que o OP precise de uma solução que funcione para N número de condições não codificadas para 2
Chris Doyle
@ Chris Doyle bom ponto, a função provavelmente deve ser estruturada como dict_factory(*args)então ... Hmmm ...
neutrino_logic
0

Não tenho certeza se você precisa usar recursão especificamente ou não, mas pode gerá-los usando o método do produto itertools para gerar todas as combinações. isso está escrito de tal maneira que você pode ter condições de 1 a n e ainda deve funcionar e devolvê-lo item por item.

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)

RESULTADO

{'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}
Chris Doyle
fonte