`yield` внутри рекурсивной процедуры - PullRequest
8 голосов
/ 03 марта 2020

Допустим, у меня есть список Python, представляющий диапазоны для некоторых переменных:

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

Он представляет эту переменную i в диапазоне от 1 до 5, а внутри этой переменной l oop 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. Где это должно быть? Как мне избежать вложенных генераторов?

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 ]

5 голосов
/ 03 марта 2020

В этом случае может быть легче сделать шаг назад и начать fre sh.

Давайте начнем с разделения ключей и интервалов, используя известный трюк, включающий zip:

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

(Соответствие между ними сохраняет исходное соединение; intervals[i] - это интервал для переменной keys[i] для всех 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 голосов
/ 03 марта 2020

Я не уверен, нужно ли вам использовать рекурсию или нет, но вы можете сгенерировать их, используя метод продукта itertools для генерации всех комбинаций. это написано так, что у вас может быть от 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}
0 голосов
/ 03 марта 2020

Вы можете, возможно, немного упростить понимание внутреннего генератора и 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

использовать как:

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