C# で遅延&メモワイズなフィボナッチ数列


LazyなFibonacci - (hatena (diary ’Nobuhisa)) にからめて。


墓に刻むほどクールではないけれど、そういえば Ruby にも特異メソッドを使って…

fib = [1,1]
def fib.[](i)
  i < size ? super : self[i] = self[i-2] + self[i-1]
end

p fib[39]  #=> 102334155


というような感じのフィボナッチ数列の実現方法があったことを思い出したので、これに倣って、C# の勉強を兼ね、見よう見まねで似たような仕組みのフィボナッチ数列にチャレンジしてみました。

using System;
using System.Collections.Generic;

class Fib : List<int>
{
    new public int this[int i]
    {
        get
        {
            if (i >= this.Count)
                this.Add(this[i - 2] + this[i - 1]);
            return base[i];
        }
    }

    new public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; ; i++)
            yield return this[i];
    }
}


class Program
{
    static void Main(string[] args)
    {
        var count = 0;
        foreach (int e in new Fib() { 1, 1 })
        {
            if (++count > 5)
                break;
            Console.WriteLine(e);
        }
    }
}


以下は余談。

当初は、taguoさんのコメントにある zipWith() を参考に、Enumerator を二つゲットしてその一方をあらかじめ MoveNext() してずらしておけば、くだんの Haskellフィボナッチ数列に似た感じにできるんじゃないかなーと思って次のように書いてみたのですが結果はあえなくNG。使用中のコレクションをいじってはいけないみたいです。

using System;
using System.Collections.Generic;

class NGfib : List<int>
{
    new public IEnumerator<int> GetEnumerator()
    {
        var i = 0;
        yield return this[i++];
        yield return this[i++];
        var e1 = base.GetEnumerator();
        var e2 = base.GetEnumerator();
        e1.MoveNext();
        while (true)
        {
            e2.MoveNext();
            e2.MoveNext();
            this.Add(e1.Current + e2.Current);
            yield return this[i++];
        }
    }
}


class Program
{
    static void Main(string[] args)
    {
        var count = 0;
        foreach (int e in new NGfib() { 1, 1 })
        {
            if (++count > 10)
                break;
            Console.WriteLine(e);
        }
    }
}

ハンドルされていない例外: System.InvalidOperationException: コレクションが変更されました。列挙操作は実行されない可能性があります。