昨天提到了可以透過抽取方法整理複雜邏輯,在Sum Consecutive的例子中,雖然把while迴圈抽出去之後多了好幾個方法,但是可以發現每個方法做的事情比較簡單且具體,讓我們更能專注在每個方法的實作。
以昨天例子來說,當我們把方法拆細,每一個方法功能看上去邏輯比較簡單,再來我們就可以繼續往每個方法實作調整。例如前兩個方法都是使用迴圈實作,就可以想想看使否可以使用LINQ來簡化。
public List<int> SumConsecutives(List<int> numbers)
{
var consecutiveSums = new List<int>();
for (var index = 0; index < numbers.Count; )
{
var consecutiveNumbers = GetConsecutiveNumbers(numbers, index);
consecutiveSums.Add(consecutiveNumbers.Sum());
index += consecutiveNumbers.Count;
}
return consecutiveSums;
}
private List<int> GetConsecutiveNumbers(List<int> numbers, int index)
{
var consecutiveNumbers = new List<int> {numbers[index]};
while (!IsLastNumber(numbers, index) && IsNumberConsecutive(numbers, index))
{
consecutiveNumbers.Add(numbers[index]);
index++;
}
return consecutiveNumbers;
}
private static bool IsNumberConsecutive(List<int> numbers, int index)
{
return numbers[index] == numbers[index + 1];
}
private bool IsLastNumber(List<int> numbers, int index)
{
return index == numbers.Count - 1;
}
GetConsecutiveNumbers(List numbers, int index)目的是找出當前index後的連續數字。我們能夠使用LINQ所提供的Paging method來簡化他。
private List<int> GetConsecutiveNumbers(List<int> numbers, int index)
{
return numbers.Skip(index)
.TakeWhile(number => number == numbers[index]).ToList();
}
關於LINQ的Paging method,之後有機會我們會在多用一點例子來聊。
在看到原本的SumConsecutives(List numbers),裡頭是把一組一組連續數字加起來並放到consecutiveSums中。原本的演算法因為Index遞增的方式不固定,沒那麼好改成LINQ形式。但是如果試著改用其他演算法,還是能以LINQ的形式來簡化代碼。例如pengzhisun的做法,相比原本的代碼,變得較為簡潔。
public List<int> SumConsecutives(List<int> numbers)
{
return numbers.Select((number, index) => index > 0 && numbers[index - 1] == number ? (int?) null : GetConsecutiveNumbers(numbers, index).Sum())
.Where(sum => sum.HasValue)
.Select(sum => sum.Value)
.ToList();
}
最後代碼長度一開始的解法長度差不多,但是透過LINQ語法和適當的方法名稱,讓最後的做法可讀性好了一些。
public List<int> SumConsecutives(List<int> numbers)
{
return numbers.Select((number, index) => index > 0 && numbers[index - 1] == number ? (int?) null : GetConsecutiveNumbers(numbers, index).Sum())
.Where(sum => sum.HasValue)
.Select(sum => sum.Value)
.ToList();
}
private List<int> GetConsecutiveNumbers(List<int> numbers, int index)
{
return numbers.Skip(index)
.TakeWhile(number => number == numbers[index]).ToList();
}
透過抽取方法來讓我們拆解原本的大方法,不但能使代碼更好閱讀理解,而且能讓我們更關注每個一個方法的實作內容,進而把每個小方法優化改善。這種Divide and Conquer思考方式,不但能運用在演算法上,更能運用在優化代碼。