Python : 재귀 알고리즘을 생성기로 사용


99

최근에 저는 사소하지 않은 제약 조건으로 특정 시퀀스를 생성하는 함수를 작성했습니다. 문제는 자연스러운 재귀 솔루션과 함께 발생했습니다. 이제 상대적으로 작은 입력의 경우에도 시퀀스가 ​​수천 개이므로 모든 시퀀스로 목록을 채우는 대신 알고리즘을 생성기로 사용하는 것을 선호합니다.

여기에 예가 있습니다. 재귀 함수를 사용하여 문자열의 모든 순열을 계산한다고 가정합니다. 다음 순진한 알고리즘은 추가 인수 '저장'을 취하고 찾을 때마다 순열을 추가합니다.

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

이 코드는 작동 하지 않습니다 (함수가 빈 생성기처럼 작동 함).

내가 뭔가를 놓치고 있습니까? 위의 재귀 알고리즘을 반복 알고리즘으로 바꾸지 않고 생성기로 바꾸는 방법이 있습니까?

답변:


117
def getPermutations(string, prefix=""):
    if len(string) == 1:
        yield prefix + string
    else:
        for i in xrange(len(string)):
            for perm in getPermutations(string[:i] + string[i+1:], prefix+string[i]):
                yield perm

또는 누산기없이 :

def getPermutations(string):
    if len(string) == 1:
        yield string
    else:
        for i in xrange(len(string)):
            for perm in getPermutations(string[:i] + string[i+1:]):
                yield string[i] + perm

29
Python 3.4에서는 마지막 두 줄을로 바꿀 수 있습니다 yield from getPermutations(string[:i] + string[i+1:]).이 방법은 여러면에서 더 효율적입니다!
Manuel Ebert

1
어떤 식 으로든 결과를 빌드해야합니다. 사용 yield from을 필요로하는 어큐뮬레이터 인수를 사용하는 ( prefix).
Markus Jarderot 2014

제안 : string[i],string[:i]+string[i+1:]쌍 을 반환하는 다른 생성기를 정의하십시오 . 그러면 다음과 같습니다.for letter,rest in first_letter_options(string): for perm in getPermuations(rest): yield letter+perm
Thomas Andrews

29

이것은 len(string)-deep 재귀를 피하고 일반적으로 제너레이터 내부 제너레이터를 처리하는 좋은 방법입니다.

from types import GeneratorType

def flatten(*stack):
    stack = list(stack)
    while stack:
        try: x = stack[0].next()
        except StopIteration:
            stack.pop(0)
            continue
        if isinstance(x, GeneratorType): stack.insert(0, x)
        else: yield x

def _getPermutations(string, prefix=""):
    if len(string) == 1: yield prefix + string
    else: yield (_getPermutations(string[:i]+string[i+1:], prefix+string[i])
            for i in range(len(string)))

def getPermutations(string): return flatten(_getPermutations(string))

for permutation in getPermutations("abcd"): print permutation

flatten다른 생성기 yield를 반복하고 yield각 항목을 수동으로 처리하는 대신 단순히 생성하여 계속 진행할 수 있습니다.


Python 3.3은 yield from구문에 추가 되어 하위 생성기에 자연스러운 위임을 허용합니다.

def getPermutations(string, prefix=""):
    if len(string) == 1:
        yield prefix + string
    else:
        for i in range(len(string)):
            yield from getPermutations(string[:i]+string[i+1:], prefix+string[i])

20

getPermutations에 대한 내부 호출-생성자이기도합니다.

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-loop로 그것을 반복해야합니다 (@MizardX 게시를 참조하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.