幾乎的所有的代碼中都會包含有迴圈,但是都沒正式聊到好好可以如何改善,今天就來說說迴圈可以怎麼調整,主要會以LINQ來改寫一些常見的迴圈形式。
把每一個元素轉換成另外一個元素,例如:
var numbers = new List<int>();
for (var stringNumber in stringNumbers)
{
numbers.Add(int.Parse(stringNumber));
}
⇒ 可以使用Select取代
var numbers = stringNumbers.Select(stringNumber => int.Parse(stringNumber));
搜尋符合條件元素,例如:
var evenNumbers = new List<int>();
for (var number in numbers)
{
if (number % 2 == 0)
{
evenNumbers.Add(number);
}
}
⇒ 可以使用Where取代
var evenNumbers = numbers.Where(numbers => numbers % 2 == 0);
計算每一個元素的得出另一個狀態或數值,例如:
int sum = 0;
for (var number in numbers)
{
sum += number;
}
⇒ 可以使用Sum取代
int sum = numbers.Sum();
int allLargeThanZero = true;
for (var number in numbers)
{
if (number <= 0)
{
allLargeThanZero = false;
}
}
⇒ 可以使用All取代
int allLargeThanZero = numbers.All(number => number <= 0);
var max = 0;
var min = 0;
for (var number in numbers)
{
if (max < number)
{
max = number;
}
if (min > number)
{
min = number;
}
}
⇒ 可以使用Max/Min取代
var max = numbers.Max(number => number);
var min = numbers.Min(number => number);
使用自定義規則去綜合每一個元素的狀態得出另一個狀態,例如:
var newString = "";
for (var str in strings)
{
newString += str[0];
}
⇒ 可以使用更泛用Aggregate取代
var newString = strings.Aggregate("", (newString, str) => newString + str);
把每一次的迭代當作下一次迭代的輸入,得出另一個狀態,例如:
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();
}
private List<int> NextCandidates(List<int> candidates, int victim)
{
return candidates.Where((_, index) => index != victim).ToList();
}
private int FindVictim(int step, int start, List<int> candidates)
{
return (start + step - 1) % candidates.Count();
}
private bool HasSurvivor(List<int> candidates)
{
return candidates.Count() == 1;
}
在這種情況中可以使用遞迴的方式來消除迴圈,讓start的狀態保存在每個方法的迭代之中,而不需要自己維護。也能讓暫存變數變成參數,避免閱讀代碼時過度關注暫存變數。
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());
return FindSurvivor(NextCandidates(candidates, victim), victim, step);
}
private List<int> NextCandidates(List<int> candidates, int victim)
{
return candidates.Where((candidate, index) => index != victim).ToList();
}
private int FindVictim(int start, int step, int count)
{
return (start + step - 1) % count;
}
private bool HasSurvivor(List<int> candidates)
{
return candidates.Count() == 1;
}
使用遞迴的方式雖然可以消除迴圈,但對於不熟悉遞迴的人來說可能會大大增加閱讀的困難。所以使用時還是得考慮實際場景,如果寫代碼的速度比較快,甚至可以把兩種版本都寫出來比較差異。
其他語言例如Javascript、Dart也都有許多方便的集合的Util方法,例如:map, reduce。寫迴圈的時候,或許可以思考一下迴圈的目的,使用更直觀的寫法。以上是自己過去經驗中比較常遇到的情境,不知道大家在寫代碼時都是什麼情況呢?