2005年C#の旅その5

今回はIteratorについて。Iteratorというとforeach専用みたいな感じで紹介されることが多いです。確かに、IEnumerator/IEnumerableを用意するのは面倒だったので大変便利ですが、Iteratorはプログラムの書き方変えてしまう可能性がある気がしています。単なるカンですけど。(^^;
Iteratorは何もC#の専売特許というわけでなく、他の言語でも既に用意されています。なので、使い方については他の言語の例から学べば良さそうです。ちょっとPythonからサンプルを引っ張ってきました。

#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace ConsoleApplication1
{
    delegate bool Predicate<T>(T value);

    /**
     * Python Cookbook 17.11 Wrapping an Unbounded Iterator to Restrict Its Output C#版
     */
    class Program
    {
        // conditionが真の間、gから値を取得する
        static IEnumerator<int> GenWhile(IEnumerator<int> g, Predicate<int> condition)
        {
            while (g.MoveNext())
            {
                if (condition(g.Current))
                    yield return g.Current;
                else
                    yield break;
            }
        }

        // n件数分gから取得する
        static IEnumerator<int> Take(int n, IEnumerator<int> g)
        {
            while (n-- > 0 && g.MoveNext()) {
                yield return g.Current;
            }
        }

        // n件飛ばしてgから取得する
        static IEnumerator<int> Drop(int n, IEnumerator<int> g)
        {
            while (n-- > 0 && g.MoveNext()) ;

            if (n > 0)
                yield break;

            while (g.MoveNext())
                yield return g.Current;
        }

        // 偶数を生成
        static IEnumerator<int> GenEven()
        {
            int n = 0;
            while (true)
            {
                n += 2;
                yield return n;
            }
        }

        static void Print(IEnumerator<int> g)
        {
            while (g.MoveNext())
                Console.Write(g.Current.ToString().PadLeft(3));
            Console.WriteLine();
        }

        static void Main(string[] args)
        {
            // 偶数から12以下のものを取り出す
            Print(GenWhile(GenEven(), delegate(int n) { return n < 12; }));
            // 偶数から小さい順に5つ取り出す
            Print(Take(5, GenEven()));
            // 偶数から最初の5つを飛ばした、つぎの5つを取り出す
            Print(Take(5, Drop(5, GenEven())));

            Console.ReadLine();
        }
    }
}

/* 結果
  2  4  6  8 10
  2  4  6  8 10
 12 14 16 18 20 */

配列を使わないプログラミングも面白いですね。