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

คำแนะนำ: กำหนดตัวสร้างอื่นที่ส่งคืน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.