尾遞歸意味着你必須直接返回遞歸調用的結果,沒有進一步的操作。
映射中明顯的遞歸是計算列表中一個元素上的函數,然後使用遞歸調用來處理列表的其餘部分。但是,您需要將處理一個元素的結果與處理列表的其餘部分的結果相結合,這需要遞歸調用之後的操作。
避免這種情況的一個很常見的模式是將組合內部的遞歸調用;你將處理後的元素作爲參數傳入,並使其成爲map
負責進行組合的一部分。
def map(l, f):
if l == []:
return []
else:
return map(l[1:], f, f(l[0]))
現在它是尾遞歸!但它顯然也是錯誤的。在尾遞歸調用中,我們傳遞了3個參數,但map只有兩個參數。然後就是我們用第三個值做什麼的問題。在基本情況下(列表爲空時),很明顯:返回一個包含傳入信息的列表。在遞歸情況下,我們計算一個新值,並從頂部傳入此額外參數,並且我們有遞歸調用。新值和額外參數需要彙總在一起傳遞給遞歸調用,以便遞歸調用可以是尾遞歸。所有這一切都表明了以下幾點:
def map(l, f):
return map_acc(l, f, [])
def map_acc(l, f, a):
if l == []:
return a
else:
b = a + [f(l[0])]
return map_acc(l[1:], f, b)
從而可以更簡潔,Pythonically表現爲其他的答案顯示,而不訴諸單獨的輔助功能。但是這顯示了將非尾遞歸函數轉換爲尾遞歸函數的一般方法。
在上面,a
被稱爲累加器。通常的想法是將通常做的操作移到遞歸調用後進入下一個遞歸調用,通過包裝工作外部調用完成「迄今」並將其傳遞到累加器中。
如果map
可以被看作意義「的l
每個元素上調用f
,並返回結果列表」,map_acc
可以被認爲是意爲「叫f
的l
每一個元素,返回列表結果結合a
,已經產生的結果列表「。
來源
2011-08-08 01:43:32
Ben
爲什麼這個函數不是尾遞歸? –
它不是因爲映射不是最後一個操作,映射的結果被添加到[f(l [0])],所以遞歸過程中的每個堆棧幀將需要 – Joe
好吧,所以你遞歸地生成元素,然後合併他們到一個列表。迭代過程會在每一步中更新列表的狀態,並將其傳遞給下一次迭代,對吧?你需要更多的幫助嗎? –