在一些迴圈使用情境,我們比較難使用Aggregation方法來簡化。有一種使用情境是需要透過暫存變數來保存每一輪的狀態,並帶到下一輪計算,此時就會讓代碼變得複雜。
舉個例子
在下面這段Josephus Survivor的Kata例子中,迴圈中會計算每一輪要移除受害者的位置,並把倖存者帶進下一輪迴圈中計算並找出下一輪的受害者。每一輪的迴圈中都需要把上一個受害者的位址紀錄在start裡。
c#
public int Play(int count, int step)
{
var candidates = Enumerable.Range(1, count).ToList();
int start = 0;
while (!HasSurvivor(candidates))
{
var victim = FindVictim(step, start, candidates);
candidates = NextCandidates(candidates, victim);
start = victim;
}
return candidates.First();
} 此時可以發現迴圈中的代碼雖然不多,卻也不好理解。在這個例子中,start不只是最一開始的初始位置,而是迴圈中的每一輪的初始位置,這個資訊必須要來回反覆的閱讀才能夠理解。
在累加數字的例子中,sum也是相似的情境,sum表示在每一輪迴圈中的暫時合計值。但是我們可以透過Aggregation方法讓sum這個暫存變數消失。與sum不同的是,start無法使用Aggregation方法簡化。
雖然無法使用Aggregation方法,但是我們可以使用遞迴來隱藏start。
c#
public int Play(int count, int step)
{
var candidates = Enumerable.Range(1, count).ToList();
return FindSurvivor(candidates, 0, step);
}
private int FindSurvivor(List<int> candidates, int start, int step)
{
if (HasSurvivor(candidates))
{
return candidates.First();
}
var victim = FindVictim(start, step, candidates.Count());
var nextCandiates = NextCandidates(candidates, victim);
return FindSurvivor(nextCandidates, victim, step);
} 當透過遞迴來重構這個例子中,我們可消去迴圈並把start隱藏在參數中,並且只表示初始位址這個意義,讓代碼更容易閱讀。
實際情況...
在實際情況中,這種使用情境並不常見,多數是在演算法的情境下會比較常使用到,例如:在Quick Sort演算法中紀錄pivot、在多個商品與多種打折策略中計算最優惠的折扣組合。
迴圈最常使用到的語法之一,用起來也相當的容易,但是要如何寫得讓人容易理解,就不是一件容易的事情。所幸的是很多語言都支援好用的Aggregation方法,讓我們更容易從把一些常見的迴圈形式簡化,使代碼更專注在其職責,而不是迴圈本身。