Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`yield` inside a recursive procedure

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

like image 340
Noel Arteche Avatar asked Mar 03 '20 17:03

Noel Arteche


1 Answers

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))
like image 119
chepner Avatar answered Oct 17 '22 00:10

chepner