Python: bir jeneratör olarak özyinelemeli bir algoritma kullanma


99

Son zamanlarda, önemsiz kısıtlamalarla belirli dizileri oluşturmak için bir işlev yazdım. Sorun, doğal özyinelemeli bir çözümle geldi. Şimdi, nispeten küçük girdiler için bile dizilerin birkaç bin olduğu, bu nedenle algoritmamı tüm dizilerle bir listeyi doldurmak için kullanmak yerine bir jeneratör olarak kullanmayı tercih ederim.

İşte bir örnek. Bir dizgenin tüm permütasyonlarını özyinelemeli bir fonksiyonla hesaplamak istediğimizi varsayalım. Aşağıdaki saf algoritma fazladan bir 'depolama' argümanı alır ve bir tane bulduğunda buna bir permütasyon ekler:

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

(Lütfen verimsizliği önemsemeyin, bu sadece bir örnektir.)

Şimdi fonksiyonumu bir üreteç haline getirmek, yani onu depolama listesine eklemek yerine bir permütasyon vermek istiyorum:

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

Bu kod yok değil (boş bir jeneratör gibi işlev davranır) çalışır.

Bir şey mi kaçırıyorum? Yukarıdaki yinelemeli algoritmayı, yinelemeli bir algoritma ile değiştirmeden bir jeneratöre dönüştürmenin bir yolu var mı ?

Yanıtlar:


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

Veya bir akümülatör olmadan:

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'te son iki satırı ile değiştirebilirsiniz yield from getPermutations(string[:i] + string[i+1:]), bu birçok yönden daha etkilidir!
Manuel Ebert

1
Yine de sonucu bir şekilde oluşturmanız gerekir. Kullanmak yield from, biriktirici bağımsız değişkenini ( prefix) kullanmanızı gerektirir .
Markus Jarderot

Öneri: string[i],string[:i]+string[i+1:]Çiftleri döndüren başka bir oluşturucu tanımlayın . O zaman şöyle olurdu:for letter,rest in first_letter_options(string): for perm in getPermuations(rest): yield letter+perm
Thomas Andrews

29

Bu, len(string)-derin özyinelemeyi önler ve genel olarak üreteçlerin içindeki üreteçleri ele almanın güzel bir yoludur:

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 başka bir jeneratörde ilerlemeye devam etmemizi sağlar. yield , içinde yinelemek ve yieldher bir öğeyi manuel olarak uygulamak yerine, .


Python 3.3, yield frombir alt jeneratöre doğal yetkilendirmeye izin veren sözdizimine eklenecektir :

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 için iç çağrı - aynı zamanda bir jeneratör.

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

Bunu bir döngü ile yinelemelisin (bkz. @MizardX gönderimi, beni saniyelerle sınırlandırdı!)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.