`yield` inside a recursive procedure

Noel Arteche :

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, {}):
    ...

chepner :

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=31622&siteId=1