Interactive Extensionsを自分で実装する – 9.Expand, Generate

Interactive Extensions(Ix)を自分で実装する、第9回目はExpandです。

このメソッドの動作を探るにはちょっとてこずりました、適当に組んだだけだと無限ループになってしまったからです。実用性は一旦横に置いて、まずは以下の使用例を見てください。

static IEnumerable<int> EmptySequence() {
    return Enumerable.Empty<int>();
}
static IEnumerable<int> Sequence1(){
    yield return 0;
}
static IEnumerable<int> Sequence2(){
    yield return 1;
    yield return 4;
}
static IEnumerable<int> Sequence3(){
    yield return 2;
    yield return 6;
}
static void ExpandTest(){
    IEnumerable<int>[] sequenceSelector = new[]{
        EmptySequence(),
        Sequence1(),
        Sequence2(),
        Sequence3()
    };
    var testEnum = Enumerable.Range(3, 1);  // 3のみ
    var expandedEnum = testEnum.Expand(i => sequenceSelector[i % 4]);
    foreach (var item in expandedEnum)
    {
        Console.WriteLine(item);
    }
}

expand
まずsourceは{3}だけのIEnumerable<int>です、これをExpandします。まずsourceを全て列挙します、この場合アイテムは3だけなのでまず3が列挙されます。次に、{3}に対しててselectorをかけたシーケンスが作成されます、この場合3%4=3でSequence3()が選択され、これを列挙します。このアイテムは{2, 6}なので最初の{3}と合わせて、{3,2,6}と列挙されることになります。

次に、この{2,6}に対して再度selectorをかけたシーケンスが作成されます、この場合2%4=2, 6%4=2でSequence2()が2回列挙されますので、先ほどの{3,2,6}に続いて{1,4}, {1,4}と続くので、{3,2,6,1,4,1,4}と列挙されることになります。

さらに{1,4,1,4}に対してselectorを適用したシーケンスが作成されますが、1%4=1, 4%4=0ですので、Seuence1()、EmptySequence(), Sequence1(), EmptySequence()と列挙されることになります。EmptySequence()は何もないので出力には無関係で、Sequence1()からは0が1回、これが2コあるので先ほどの{3,2,6,1,4,1,4}に続いて、{3,2,6,1,4,1,4,0,0}と続きます。

さらに{0}が2回列挙されていますがこれはEmptySequence()なので列挙アイテムに変化はなく最終出力も{3,2,6,1,4,1,4,0,0}となるわけです。

では実装してみます。(Let’s Implement It!)

Expand…幅優先探索でシーケンス列挙を繰り返す

namespace EmulateInteractiveExtensions
{
    public static class EmulateIxExtensions
    {
        public static IEnumerable<TSource> Expand<TSource>(this IEnumerable<TSource> source,
                                                           Func<TSource, IEnumerable<TSource>> selector)
        {
            if (source == null || selector == null){
                throw new ArgumentNullException();
            }
            var sourcesA = new Queue<IEnumerable<TSource>>();
            var sourcesB = new Queue<IEnumerable<TSource>>();
            Queue<IEnumerable<TSource>> currentSources = null,
                                        nextSources = null;

            bool side = true;
            sourcesA.Enqueue(source);
            do
            {
                currentSources = side ? sourcesA : sourcesB;
                nextSources = side ? sourcesB : sourcesA;
                do
                {
                    var childSource = currentSources.Dequeue();
                    foreach (var item in childSource)
                    {
                        yield return item;
                        nextSources.Enqueue(selector(item));
                    }
                } while (currentSources.Count() != 0);
                side = !side;
            } while (nextSources.Count() != 0);
        }
    }
}

sourceシーケンスを列挙しつつselectorをかけたシーケンスをAに保持、次にシーケンスAを列挙しつつselectorをかけたシーケンスをBに保持、その次にシーケンスBを列挙しつつselectorをかけたシーケンスをAに保持、…と繰り返していく実装になります。使い方を間違えるとすぐに無限ループになるのでご注意。

 

次はGenerate。このシグネチャはこんな感じでややこしく見えますが・・・

public static IEnumerable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector);

これは使い方を見れば簡単じゃんって思うことでしょう、と同時に利用価値も理解いただけると思います。

static void GenerateTest()
{
    var testSequence = EmulateIxExtensions.Generate(0, i => i < 10, i => i + 1, i => Math.Exp(i));
    foreach (var item in testSequence)
    {
        Console.WriteLine(item);
    }
}

generate
そう、forの構文そっくり!であれば説明は不要ですね。では実装。Let’s Implement It!

Generate…forステートメント類似の構文でシーケンスを生成する。

namespace EmulateInteractiveExtensions
{
    public static class EmulateIxExtensions
    {
        public static IEnumerable<TResult> Generate<TState, TResult>(TState initialState,
                                                                     Func<TState, bool> condition,
                                                                     Func<TState, TState> iterate,
                                                                     Func<TState, TResult> resultSelector)
        {
            if (condition == null || iterate == null || resultSelector == null)
            {
                throw new ArgumentNullException();
            }
            TState current = initialState;
            while (condition(current))
            {
                yield return resultSelector(current);
                current = iterate(current);
            }
        }
    }
}

Interactive Extensionsの利用価値はForEachだけじゃないですよ!Generateスキ。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中