遞歸有時很難理解,只是看代碼。在精神層面跟蹤堆放的內容以及檢索內容和時間可能會很快耗盡我們的工作記憶。在遞歸樹的層次結構中繪製每個段落的路徑可能是有用的,這是我所做的嘗試回答你的問題。
爲了理解這個例子中的工作原理,首先我們必須認識到第一章中存在兩個不同的階段,第一階段是在遞歸之前執行的代碼,第二階段是代碼在它之後執行。
(更好地解釋流,我添加了一些變量的原代碼)
# Clause 1
def split(in_list, count) when count > 0 do
# FIRST STAGE
[head | tail] = in_list
# RECURSION
result = split(tail, count - 1)
# SECOND STAGE
{left, right} = result
return = {[head | left], right}
end
#Clause 2
def split(list, _count), do: return = {[], list}
現在,在繼續閱讀,請看看代碼,並嘗試回答這些問題:
- 經過第一次塊的迭代次數,第一次綁定
result
變量?
- 遞歸第
split(tail, count - 1)
將在第1條中被調用多少次?
- 第2條
split(list, _count)
將被調用多少次?
- 第2條款的作用是什麼?
現在比較你的答案在看這個模式,顯示每一個通道和它的層次結構:
(作爲一個例子,我們的名單[1, 2, 3, 4, 5]
它的第三個元素拆分後獲得的元組{[1, 2, 3], [4, 5]}
)
split([1,2,3,4,5], 3)
> FIRST STAGE of CLAUSE 1/ITERATION 1 called as: split([1, 2, 3, 4, 5], 3):
Got 'head'=1, 'tail'=[2, 3, 4, 5], 'count'=3
now I'm going to iterate passing the tail [2, 3, 4, 5],
Clause 1 will match as the counter is still > 0
> FIRST STAGE of CLAUSE 1/ITERATION 2 called as: split([2, 3, 4, 5], 2):
Got 'head'=2, 'tail'=[3, 4, 5], 'count'=2
now I'm going to iterate passing the tail [3, 4, 5],
Clause 1 will match as the counter is still > 0
> FIRST STAGE of CLAUSE 1/ITERATION 3 called as: split([3, 4, 5], 1):
Got 'head'=3, 'tail'=[4, 5], 'count'=1
Now the counter is 0 so I've reached the split point,
and the Clause 2 instead of Clause 1 will match at the next iteration
> Greetings from CLAUSE 2 :-), got [4, 5], returning {[], [4, 5]}
< Im BACK to the SECOND STAGE of ITERATION 3
got result from CLAUSE 2: {[], [4, 5]}
{left, right} = {[], [4, 5]}
Now I'm build the return value as {[head | left], right},
prepending 'head' (now is 3) to the previous value
of 'left' (now is []) at each iteration,
'right' instead is always [4, 5].
So I'm returning {[3], [4, 5]} to iteration 2
< Im BACK to the SECOND STAGE of ITERATION 2
got result from previous Clause 1/Iteration 3, : {[3], [4, 5]}
{left, right} = {[3], [4, 5]}
Now I'm build the return value as {[head | left], right},
prepending 'head' (now is 2) to the previous value
of 'left' (now is [3]) at each iteration,
'right' instead is always [4, 5].
So I'm returning {[2, 3], [4, 5]} to iteration 1
< Im BACK to the SECOND STAGE of ITERATION 1
got result from previous Clause 1/Iteration 2, : {[2, 3], [4, 5]}
{left, right} = {[2, 3], [4, 5]}
Now I'm build the return value as {[head | left], right},
prepending 'head' (now is 1) to the previous value
of 'left' (now is [2, 3]) at each iteration,
'right' instead is always [4, 5].
And my final return is at least: {[1, 2, 3], [4, 5]}
{[1, 2, 3], [4, 5]}
在該模式中,開始每次迭代標有
> FIRST STAGE of CLAUSE 1/ITERATION n called as: ...
同時迭代的延續年初被標記爲
< I'm BACK to the SECOND STAGE of ITERATION n
現在,我們可以清楚地看到:
- 第一塊被重複三次;
- 第2條被稱爲一次;
- 第二塊重複三次,第一次收到第2項的結果,第1項的剩餘時間;
- 第2的結果中包含的分裂列表的右側部分,在計算第1項目
所以第三次迭代,什麼是第2條的作用?這是一種技巧,一種回傳的方式,直到迭代的繼續,否則分割列表的右側部分不可訪問的值。
這是代碼的一步一步的解釋:
在第一階段函數,變量我稱爲in_list
的第一個參數的值,在被分解的head
和tail
組件:
# FIRST STAGE
[head | tail] = in_list
則head
被壓入堆棧和tail
和更新counter
被傳遞到遞歸:
result = split(tail, count - 1)
count
迭代之後,所有左分離元素都在堆棧中,所有右分裂元素都打包在tail
中。第2條現在被稱爲。
在第2章調用之後,遞歸繼續第二階段,其中result
變量被綁定到前一個split/2
迭代返回的兩個(部分)分割列表。
現在,在每次迭代,我們提取的左側和右側列表弗朗結果:
{左,右} =導致
,並加入到從棧中彈出的left
的head
(這是在第一階段計算),將結果返回給呼叫者:
return = {[head | left], right}
因此在每次迭代中左邊部分增長'直到最終值。
第一個result
由條款2返回,當迭代達到分割點時匹配,即當count = 0
。 (第2條只會觸發一次)。所有後續的result
將由第1條迭代的摺疊第二階段返回。
這是打印上面的架構代碼:
def split(in_list, count), do: split(in_list, count, 1)
# Clause 1
def split(in_list=[head | tail], count, iteration) when count > 0 do
offset = String.duplicate " ", 5 * (iteration - 1)
IO.puts offset <> "> FIRST STAGE of CLAUSE 1/ITERATION #{inspect iteration} called as: split(#{inspect in_list}, #{inspect(count)}):"
IO.puts offset <> " Got 'head'=#{inspect head}, 'tail'=#{inspect tail}, 'count'=#{inspect count}"
if (count - 1) > 0 do
IO.puts offset <> " now I'm going to iterate passing the tail #{inspect(tail)},"
IO.puts offset <> " Clause 1 will match as the counter is still > 0"
else
IO.puts offset <> " Now the counter is 0 so I've reached the split point,"
IO.puts offset <> " and the Clause 2 instead of Clause 1 will match at the next iteration"
end
result = split(tail, count-1, iteration + 1)
IO.puts offset <> "< Im BACK to the SECOND STAGE of ITERATION #{inspect(iteration)}"
if (count - 1) == 0 do
IO.puts offset <> " got result from CLAUSE 2: #{inspect result}"
else
IO.puts offset <> " got result from previous Clause 1/Iteration #{iteration + 1}, : #{inspect result}"
end
IO.puts offset <> " {left, right} = #{inspect result}"
{left, right} = result
IO.puts offset <> " Now I'm build the return value as {[head | left], right},"
IO.puts offset <> " prepending 'head' (now is #{inspect head}) to the previous value"
IO.puts offset <> " of 'left' (now is #{inspect left}) at each iteration,"
IO.puts offset <> " 'right' instead is always #{inspect right}."
return = {[head | left], right}
if (iteration > 1) do
IO.puts offset <> " So I'm returning #{inspect return} to iteration #{inspect(iteration - 1)}"
else
IO.puts offset <> " And my final return is at least: #{inspect return} "
end
return
end
# Clause 2
def split(list, _count, _iteration) do
IO.puts ""
IO.puts "> Greetings from CLAUSE 2 :-), got #{inspect(list)}, returning #{inspect({[], list})}"
IO.puts ""
{[], list}
end
希望這能幫助澄清一點採取的策略和內部遞歸機制。
(我的英語不是很好,希望有人能解決這個文本)
請記住,儘管此代碼_is_優雅,它實際上並沒有複製Enum.split'的'的行爲時'count'是一個負數,或者計數大於列表的長度。 –
謝謝@CodyPoll!是的...我知道這部分代碼只實現了原來的'Enum.split'行爲的一部分。 –