`yield` inside a recursive procedure
Asked Answered
F

3

9

Let's say I have a Python list representing ranges for some variables:

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

This represents that variable i ranges from 1 to 5, and inside that loop variable j ranges from 1 to 2. I want a dictionary for each possible combination:

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

The reason is that I want to iterate over them. But because the whole space is too big, I do not want to generate all of them, store them and then iterate over that list of dictionaries. I thought about using the following recursive procedure, but I need some help with the yield part. Where should it be? How do I avoid nested generators?

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)

Now I would like to be able to do:

for valued_indices in iteration(conditions, 0, {}):
    ...

Funnelform answered 3/3, 2020 at 17:42 Comment(1)
Simply replace yield with yield from in the very last line of your function.Eulau
S
6

This is a case where it might be easier to take a step back and start fresh.

Let's start by getting the keys and the intervals separate, using a well-known trick involving zip:

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

(The correspondence between the two preserves the original pairing; intervals[i] is the interval for the variable keys[i] for all i.)

Now, let's create proper range objects from those intervals

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

We can compute the product of these range objects

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

which you should recognize as the values to use for each dict. You can zip each of those values with the keys to create an appropriate set of arguments for the dict command. For example:

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

Putting this all together, we can iterate over the product to produce each dict in turn, yielding it.

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))
Scepter answered 3/3, 2020 at 18:18 Comment(0)
S
0

You can perhaps simplify a little with an internal generator comprehension and yield from it:

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 as:

foo = dict_factory(5, 2)
while True:
    print(next(foo))
Sigmatism answered 3/3, 2020 at 18:12 Comment(2)
Given conditions is passed as list of lists i suspect the OP needs a solution that would work for N number of conditions not hardcoded to 2Heindrick
@Chris Doyle good point, the function should probably be structured as dict_factory(*args) then... Hmmm...Sigmatism
H
0

I am not sure if you specifically need to use recursion or not but you could generate these using itertools product method to generate all the combinations. this is written in such away that you can have 1 to n conditions and it should still work and return you item by 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)

OUTPUT

{'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}
Heindrick answered 3/3, 2020 at 18:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.