Böyle bir soruyu cevaplamaya çalışırken, çözüm olarak önerdiğiniz kodun sınırlamalarını gerçekten vermeniz gerekir. Sadece performanslar hakkında olsaydı, çok fazla umursamadım, ancak çözüm olarak önerilen kodların çoğu (kabul edilen cevap dahil), 1000'den fazla derinliğe sahip herhangi bir listeyi düzleştiremez.
Kodların çoğunu söylediğimde ben özyineleme hiçbir şekilde kullanan tüm kodları anlamına (veya özyinelemeli olan standart kütüphane işlevi çağırmak). Tüm bu kodlar başarısız olur, çünkü yapılan her özyinelemeli çağrı için (çağrı) yığını bir birim büyür ve (varsayılan) python çağrı yığını 1000 büyüklüğündedir.
Çağrı yığınına çok aşina değilseniz, belki aşağıdakiler yardımcı olacaktır (aksi takdirde sadece Uygulama'ya geçebilirsiniz ).
Çağrı yığını boyutu ve özyinelemeli programlama (zindan benzetmesi)
Hazineyi bulmak ve çıkmak
Bir hazine arayan, numaralı odalar ile büyük bir zindan girdiğinizi düşünün . Yeri bilmiyorsun ama hazineyi nasıl bulacağına dair bazı göstergelerin var . Her bir gösterge bir bilmecedir (zorluk değişir, ancak ne kadar zor olacağını tahmin edemezsiniz). Zaman kazanmak için bir strateji hakkında biraz düşünmeye karar verirsiniz, iki gözlem yaparsınız:
- Oraya ulaşmak için (potansiyel olarak zor) bilmeceleri çözmeniz gerekeceğinden hazineyi bulmak zor (uzun).
- Hazine bulunduğunda, girişe geri dönmek kolay olabilir, sadece aynı yolu diğer yönde kullanmanız gerekir (ancak yolunuzu hatırlamak için biraz belleğe ihtiyaç duyar).
Zindana girerken, burada küçük bir not defteri görüyorsunuz . Bilmeceyi çözdükten sonra çıktığınız her odayı (yeni bir odaya girerken) yazmak için kullanmaya karar verirsiniz, böylece girişe geri dönebilirsiniz. Bu dahice bir fikir, stratejinizi uygulamak için bir kuruş bile harcamazsınız .
Zindana giriyorsunuz, ilk 1001 bilmeceyi büyük bir başarıyla çözüyorsunuz, ancak burada planlayamadığınız bir şey geliyor, ödünç aldığınız not defterinde boş alanınız yok. Hazineye sahip olmayı zindanın içinde sonsuza dek kaybolmaktan (gerçekten akıllı görünüyor) tercih ettiğiniz için görevinizden vazgeçmeye karar veriyorsunuz.
Özyinelemeli bir program yürütme
Temel olarak, hazineyi bulmakla aynı şey. Zindan bilgisayarın hafızasıdır , şimdi amacınız bir hazine bulmak değil, belirli bir işlevi hesaplamak ( belirli bir x için f (x) bul ). Endikasyonlar basitçe f (x) 'i çözmenize yardımcı olacak alt rutinlerdir . Stratejiniz çağrı yığını stratejisi ile aynıdır , dizüstü bilgisayar yığınıdır, odalar fonksiyonların dönüş adresleridir:
x = ["over here", "am", "I"]
y = sorted(x) # You're about to enter a room named `sorted`, note down the current room address here so you can return back: 0x4004f4 (that room address looks weird)
# Seems like you went back from your quest using the return address 0x4004f4
# Let's see what you've collected
print(' '.join(y))
Zindanda karşılaştığınız sorun burada aynı olacak, çağrı yığını sonlu bir boyuta sahip (burada 1000) ve bu nedenle, geri dönmeden çok fazla işlev girerseniz, çağrı yığınını dolduracak ve görünen bir hataya sahip olacaksınız. gibi "Sevgili maceraperest, çok üzgünüm ama dizüstü dolu" : RecursionError: maximum recursion depth exceeded
. Çağrı yığınını doldurmak için özyinelemeye ihtiyacınız olmadığını unutmayın, ancak özyinelemesiz bir programın geri dönmeden 1000 işlevini çağırması pek olası değildir. Ayrıca, bir işlevden döndüğünüzde, çağrı yığınının kullanılan adresten kurtarıldığını (dolayısıyla "yığının" adı, bir işleve girmeden önce geri dönüş adresinin içeri aktarıldığını ve geri dönerken çıkarıldığını da anlamanız önemlidir. Özel bir özyineleme durumunda (bir işlevf
kendini bir kez tekrar tekrar çağırırsınız) f
, hesaplama bitene kadar (hazine bulunana kadar ) tekrar tekrar gireceksiniz ve ilk etapta f
aradığınız yere geri dönene kadar geri döneceksiniz f
. Çağrı yığını, tüm dönüş adreslerinden birbiri ardına serbest bırakılacak olana kadar hiçbir şeyden asla kurtarılmayacaktır.
Bu sorun nasıl önlenir?
Bu aslında oldukça basit: "ne kadar derine inebileceğini bilmiyorsanız özyineleme kullanmayın". Bazı durumlarda, Kuyruk Çağrısı özyinelemesi Optimize Edilebilir (TCO) olduğu için bu her zaman doğru değildir . Ancak python'da durum böyle değildir ve "iyi yazılmış" özyinelemeli fonksiyon bile yığın kullanımını optimize etmez . Guido'dan bu soru hakkında ilginç bir yazı var: Tail Recursion Elimination .
Herhangi bir özyinelemeli işlevi yinelemeli hale getirmek için kullanabileceğiniz bir teknik vardır, bu teknik kendi dizüstü bilgisayarınızı getirebiliriz . Örneğin, özel durumumuzda, sadece bir liste keşfediyoruz, bir odaya girmek bir alt listeye girmeye eşdeğerdir, kendinize sormanız gereken soru , bir listeden üst listesine nasıl geri dönebilirim? Cevap o kadar karmaşık değil stack
, boş olana kadar aşağıdakileri tekrarlayın :
- Geçerli listeyi itmek
address
ve index
bir de stack
yeni bir alt listesini girerek (not listesi adresi + indeks bu nedenle biz sadece çağrı yığını tarafından kullanılan aynı tekniği kullanabilirsiniz, aynı zamanda bir adresi olduğunu);
- bir öğe her bulunduğunda öğeyi
yield
(veya listeye ekleyin);
- bir liste tamamen keşfedildikten sonra,
stack
return address
(ve index
) tuşlarını kullanarak üst listeye geri dönün .
Bunun, bazı düğümlerin alt liste A = [1, 2]
ve bazılarının basit öğeler olduğu bir ağaçtaki DFS'ye eşdeğer olduğuna dikkat edin : 0, 1, 2, 3, 4
(for L = [0, [1,2], 3, 4]
). Ağaç şöyle görünür:
L
|
-------------------
| | | |
0 --A-- 3 4
| |
1 2
DFS çapraz geçiş ön sırası şöyledir: L, 0, A, 1, 2, 3, 4. Bir yinelemeli DFS uygulamak için ayrıca bir yığına "ihtiyacınız" olduğunu unutmayın. Daha önce önerdiğim uygulama aşağıdaki durumlar ( stack
ve için flat_list
) ile sonuçlanır :
init.: stack=[(L, 0)]
**0**: stack=[(L, 0)], flat_list=[0]
**A**: stack=[(L, 1), (A, 0)], flat_list=[0]
**1**: stack=[(L, 1), (A, 0)], flat_list=[0, 1]
**2**: stack=[(L, 1), (A, 1)], flat_list=[0, 1, 2]
**3**: stack=[(L, 2)], flat_list=[0, 1, 2, 3]
**3**: stack=[(L, 3)], flat_list=[0, 1, 2, 3, 4]
return: stack=[], flat_list=[0, 1, 2, 3, 4]
Bu örnekte, giriş listesinin (ve dolayısıyla ağacın) derinliği 2 olduğu için yığın maksimum boyutu 2'dir.
uygulama
Uygulama için, python'da basit listeler yerine yineleyicileri kullanarak biraz basitleştirebilirsiniz. Alt listelerin dönüş adreslerini (hem liste adresine hem de dizine sahip olmak yerine) depolamak için (alt) yineleyicilere referanslar kullanılacaktır . Bu büyük bir fark değil ama bunun daha okunabilir olduğunu düşünüyorum (ve ayrıca biraz daha hızlı):
def flatten(iterable):
return list(items_from(iterable))
def items_from(iterable):
cursor_stack = [iter(iterable)]
while cursor_stack:
sub_iterable = cursor_stack[-1]
try:
item = next(sub_iterable)
except StopIteration: # post-order
cursor_stack.pop()
continue
if is_list_like(item): # pre-order
cursor_stack.append(iter(item))
elif item is not None:
yield item # in-order
def is_list_like(item):
return isinstance(item, list)
Ayrıca, bu haber is_list_like
Ben isinstance(item, list)
daha girdi türlerini işlemek için değiştirilebilir olan, burada ben sadece istediğim (iterable) sadece bir liste sade hâli olması. Ama bunu da yapabilirsiniz:
def is_list_like(item):
try:
iter(item)
return not isinstance(item, str) # strings are not lists (hmm...)
except TypeError:
return False
Bu, dizeleri "basit öğeler" olarak kabul eder ve bu nedenle flatten_iter([["test", "a"], "b])
geri döner ["test", "a", "b"]
ve döndürmez ["t", "e", "s", "t", "a", "b"]
. Bu durumda, iter(item)
her öğede iki kez çağrıldığına dikkat edin, okuyucunun bunu daha temiz hale getirmesi için bir alıştırma yapalım.
Test ve diğer uygulamalar hakkında açıklamalar
Sonunda, L
kullanarak sonsuz sayıda iç içe bir liste yazdıramayacağınızı unutmayın, print(L)
çünkü dahili olarak __repr__
( RecursionError: maximum recursion depth exceeded while getting the repr of an object
) için özyinelemeli çağrılar kullanacaktır . Aynı nedenle, çözümleri flatten
içeren str
aynı hata iletisiyle başarısız olur.
Çözümünüzü test etmeniz gerekiyorsa, basit bir iç içe liste oluşturmak için bu işlevi kullanabilirsiniz:
def build_deep_list(depth):
"""Returns a list of the form $l_{depth} = [depth-1, l_{depth-1}]$
with $depth > 1$ and $l_0 = [0]$.
"""
sub_list = [0]
for d in range(1, depth):
sub_list = [d, sub_list]
return sub_list
Hangi verir: build_deep_list(5)
>>> [4, [3, [2, [1, [0]]]]]
.