Python: sử dụng thuật toán đệ quy làm trình tạo


99

Gần đây, tôi đã viết một hàm để tạo ra các chuỗi nhất định với các ràng buộc không đáng kể. Vấn đề đến với một giải pháp đệ quy tự nhiên. Bây giờ nó xảy ra rằng, ngay cả đối với đầu vào tương đối nhỏ, các chuỗi là vài nghìn, vì vậy tôi muốn sử dụng thuật toán của mình như một trình tạo thay vì sử dụng nó để điền vào một danh sách với tất cả các chuỗi.

Đây là một ví dụ. Giả sử chúng ta muốn tính toán tất cả các hoán vị của một chuỗi bằng một hàm đệ quy. Thuật toán ngây thơ sau đây nhận thêm một đối số 'bộ nhớ' và thêm một hoán vị vào nó bất cứ khi nào nó tìm thấy:

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

(Xin đừng quan tâm đến sự kém hiệu quả, đây chỉ là một ví dụ.)

Bây giờ tôi muốn biến hàm của mình thành một trình tạo, tức là để mang lại một hoán vị thay vì thêm nó vào danh sách lưu trữ:

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

Mã này không hoạt động (chức năng hoạt động giống như một trình tạo trống).

Tui bỏ lỡ điều gì vậy? Có cách nào để biến thuật toán đệ quy trên thành một trình tạo mà không cần thay thế nó bằng một thuật toán lặp không?

Câu trả lời:


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

Hoặc không có bộ tích lũy:

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
Trong Python 3.4, bạn có thể thay thế hai dòng cuối cùng bằng yield from getPermutations(string[:i] + string[i+1:]), điều này hiệu quả hơn theo nhiều cách!
Manuel Ebert

1
Bạn vẫn cần xây dựng kết quả theo một cách nào đó. Việc sử dụng yield fromsẽ yêu cầu bạn sử dụng đối số tích lũy ( prefix).
Markus Jarderot

Gợi ý: Xác định một trình tạo khác trả về string[i],string[:i]+string[i+1:]các cặp. Sau đó, nó sẽ là:for letter,rest in first_letter_options(string): for perm in getPermuations(rest): yield letter+perm
Thomas Andrews

29

Điều này tránh len(string)đệ quy -deep và nói chung là một cách hay để xử lý máy phát-bên trong-máy phát:

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

flattencho phép chúng tôi tiếp tục tiến trình trong trình tạo khác bằng cách chỉ cần nhập yieldnó, thay vì lặp lại và nhập yieldtừng mục theo cách thủ công.


Python 3.3 sẽ thêm yield fromvào cú pháp, cho phép ủy quyền tự nhiên cho trình tạo con:

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

Lời gọi bên trong đến getPermutations - nó cũng là một máy phát điện.

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])  # <-----

Bạn cần lặp lại điều đó bằng vòng lặp for (xem bài đăng trên @MizardX, điều này đã giúp tôi vượt qua từng giây!)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.