Недавно я написал функцию для генерации определенных последовательностей с нетривиальными ограничениями. Проблема пришла с естественным рекурсивным решением. Теперь случается, что даже для относительно небольшого ввода последовательности составляют несколько тысяч, поэтому я предпочел бы использовать свой алгоритм в качестве генератора, а не использовать его для заполнения списка всеми последовательностями.
Вот пример. Предположим, мы хотим вычислить все перестановки строки с рекурсивной функцией. Следующий наивный алгоритм берет дополнительный аргумент «хранилище» и добавляет к нему перестановку всякий раз, когда он ее находит:
def getPermutations(string, storage, prefix=""):
if len(string) == 1:
storage.append(prefix + string) # <-----
else:
for i in range(len(string)):
getPermutations(string[:i]+string[i+1:], storage, prefix+string[i])
storage = []
getPermutations("abcd", storage)
for permutation in storage: print permutation
(Пожалуйста, не обращайте внимания на неэффективность, это только пример.)
Теперь я хочу превратить свою функцию в генератор, то есть получить перестановку вместо добавления ее в список хранения:
def getPermutations(string, prefix=""):
if len(string) == 1:
yield prefix + string # <-----
else:
for i in range(len(string)):
getPermutations(string[:i]+string[i+1:], prefix+string[i])
for permutation in getPermutations("abcd"):
print permutation
Этот код не работает (функция ведет себя как пустой генератор).
Я что-то упускаю? Есть ли способ превратить описанный выше рекурсивный алгоритм в генератор, не заменяя его итеративным ?
yield from getPermutations(string[:i] + string[i+1:])
, что во многих отношениях более эффективно!yield from
потребует от вас использования аргумента аккумулятора (prefix
).string[i],string[:i]+string[i+1:]
пары. Тогда это будет:for letter,rest in first_letter_options(string): for perm in getPermuations(rest): yield letter+perm
Это позволяет избежать
len(string)
-глубокой рекурсии и в целом является хорошим способом работы с генераторами внутри генераторов:flatten
позволяет нам продолжить работу в другом генераторе, простоyield
вставив его, вместо того, чтобы перебирать его иyield
вводить каждый элемент вручную.Python 3.3 добавит
yield from
синтаксис, который позволяет естественное делегирование суб-генератору:источник
Внутренний вызов getPermutations - это тоже генератор.
Вам нужно повторить это с помощью цикла for (см. Публикацию @MizardX, которая вытеснила меня на секунды!)
источник