Niedawno napisałem funkcję do generowania pewnych sekwencji z nietrywialnymi ograniczeniami. Problem pojawił się z naturalnym rozwiązaniem rekurencyjnym. Teraz zdarza się, że nawet dla stosunkowo niewielkich danych wejściowych sekwencje są po kilka tysięcy, dlatego wolałbym użyć mojego algorytmu jako generatora, zamiast wypełniać listę wszystkimi sekwencjami.
Oto przykład. Załóżmy, że chcemy obliczyć wszystkie permutacje łańcucha z funkcją rekurencyjną. Poniższy naiwny algorytm pobiera dodatkowy argument `` pamięć '' i dołącza do niego permutację, gdy tylko ją znajdzie:
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
(Proszę nie przejmować się nieefektywnością, to tylko przykład).
Teraz chcę przekształcić moją funkcję w generator, tj. Aby uzyskać permutację zamiast dołączać ją do listy pamięci:
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
Ten kod nie działa (funkcja zachowuje się jak pusty generator).
Czy coś mi brakuje? Czy istnieje sposób na przekształcenie powyższego algorytmu rekurencyjnego w generator bez zastępowania go algorytmem iteracyjnym ?
yield from getPermutations(string[:i] + string[i+1:])
, co jest bardziej wydajne na wiele sposobów!yield from
wymagałoby użycia argumentu akumulatora (prefix
).string[i],string[:i]+string[i+1:]
pary. Wtedy wyglądałoby to tak:for letter,rest in first_letter_options(string): for perm in getPermuations(rest): yield letter+perm
Pozwala to uniknąć
len(string)
rekurencji -głębokiej i jest ogólnie dobrym sposobem obsługi generatorów-wewnątrz-generatorów:flatten
pozwala nam kontynuować postęp w innym generatorze, po prostuyield
go używając, zamiast iterować go iyield
ręcznie wprowadzać każdy element.Python 3.3 doda
yield from
do składni, co pozwala na naturalną delegację do generatora podrzędnego:źródło
Wywołanie wewnętrzne getPermutations - to też jest generator.
Musisz to powtórzyć za pomocą pętli for (zobacz post na @MizardX, który wydłużył mnie o kilka sekund!)
źródło