C# の for 文を LINQ に置き換えたい
LINQ
を使い慣れると
LINQ
は便利ですよね。通常のループを書く時には、データがループの先頭から最後まで行ったり来たりするので、それを追って視線も思考もループ内をグルグル回ります。ところが LINQ
だと、データは上から下へ一直線に降りていくので、流れに従ってキーボードを走らせることができ、視線も上から下へと降りるだけです。もう全部 LINQ
で書きたい、ループは foreach
だけでいい、と思うくらい楽です。
そんな中、for
や while
を使うと思うのです。「このデータ、ToList()
したい! Sum()
したい! Aggregate()
したい!」と。もちろん、IEnumerable<T>
を返すメソッドを作って yield return
すればできるんですけれど、だったら素直に List.Add()
の方が面倒がない。
拡張メソッドの作成
for
に関してでも、単純なカウンタなら Enumerable.Range()
で行けるけれども、完全に置き換えることはできない。じゃあ、というのでこういう拡張メソッドを作ってみました。
public static class MyLinq
{
public static IEnumerable<T> Recur<T>(this T target, Func<T, T> func)
{
var p = target;
while (true)
{
yield return p;
p = func(p);
}
}
}
例えばこのように使います。
var data = 0.Rucur(i => i + 1).TakeWhile(i => i < 10);
foreach (var datum in data)
{
Console.WriteLine(datum);
}
これは以下の二つと全く同じ動作をします。
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
var data = Enumerable.Range(0, 10);
foreach (var datum in data)
{
Console.WriteLine(datum);
}
かえって複雑になってない?
その通りです。では次はどうでしょう?
for (int i = 2; i < 1000; i = i * i)
{
Console.WriteLine(i);
}
これを Enumerable.Range()
を使って処理するのは少し面倒くさそうです。Rucur
を使うと
var data = 2.Rucur(i => i * i).TakeWhile(i => i < 1000);
foreach (var datum in data)
{
Console.WriteLine(datum);
}
まったく同じ手順でできました。
フィボナッチ数列
それでは今度は、フィボナッチ数列を作ってみましょう。
var data = new[] { 0, 1 }
.Recur(i => new[] { i[1], i[0] + i[1] })
.Select(i => i[0])
.TakeWhile(i => i < 100000000);
foreach (var datum in data)
{
Console.WriteLine(datum);
}
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269
2178309
3524578
5702887
9227465
14930352
24157817
39088169
63245986
ニュートン-ラフソン法
ニュートン・ラフソン法を用いて、3 の平方根を求めてみましょう。
var x = 3;
var result = ((double)x / 2)
.Recur(i => (i + x / i) / 2)
.ElementAt(10);
Console.WriteLine(result);
1.73205080756888
for
ループは漸化式だった
ここまで見てきて気が付かれましたでしょうか?「フィボナッチ? ニュートン-ラフソン? それって漸化式を使うよね?」
Wikipedia - 漸化式
数学における漸化式(ぜんかしき、英: recurrence relation; 再帰関係式)は、各項がそれ以前の項の函数として定まるという意味で数列を再帰的に定める等式である。
「再帰的に定める」とは難しい言い回しですが、i = i + 1
と言うのを見てください。ほら、i
によって次の i
が決まっています。再帰してますね?
for
ループの条件は、初期値、終了条件、漸化式から成り立っていて、そうでない場合も初期値、終了条件、漸化式として一般化できます。初期値は与えられ、終了条件は TakeWhile()
もしくは Take()
で決定できるとするなら、あとは漸化式さえ実装すれば for
ループを置き換えることができることになります。これが Recur
の役目です。
執筆日: 2017/03/28
Discussion