Zipper (3)

いかに簡単に Converter<IEnumerator[], T> を作るかがポイント?

        // code formatted by http://manoli.net/csharpformat/

        /// <summary> Zip 列挙を実施します。 </summary>
        public static IEnumerable<T> Zip<T>(Converter<IEnumerator[], T> zipper, params IEnumerator[] enumerators)
        {
            if (zipper == null) { throw new ArgumentNullException("zipper"); }
            if (enumerators == null) { throw new ArgumentNullException("enumerators"); }

            while (MoveNext(enumerators))
            {
                yield return zipper(enumerators);
            }
        }

        /// <summary> Zip 列挙を実施します。 </summary>
        public static IEnumerable<T> Zip<T>(params IEnumerator[] enumerators)
        {
            if (enumerators == null) { throw new ArgumentNullException("enumerators"); }

            return Zip(GenerateConverter<T>(enumerators), enumerators);
        }

というメソッドが用意されていれば、テストクラスはデータ型のみに専念して、

    public class Pair<K, V>
    {
        public struct Item
        {
            private readonly K key;
            private readonly V value;

            public Item(K key, V value)
            {
                this.key = key;
                this.value = value;
            }

            public K Key { get { return this.key; } }
            public V Value { get { return this.value; } }
        }

        public static IEnumerable<Pair<K, V>.Item> Zip(IEnumerable<K> listOfKey, IEnumerable<V> listOfValue)
        {
            return Zip(listOfKey.GetEnumerator(), listOfValue.GetEnumerator());
        }

        public static IEnumerable<Pair<K, V>.Item> Zip(IEnumerator<K> listOfKey, IEnumerator<V> listOfValue)
        {
            return Zipper.Zip<Pair<K, V>.Item>(listOfKey, listOfValue);
        }
    }

    namespace Test
    {
        using MyMap = Pair<int, string>;

        [TestFixture]
        public class Scenario1
        {
            [Test]
            public void Case1()
            {
                int[] keys = { 1, 3, 2, 4 };
                string[] values = { "a", "b", "x", "d", "f" };

                foreach (MyMap.Item item in MyMap.Zip(keys, values))
                {
                    Debug.WriteLine(item.Key);
                    Debug.WriteLine(item.Value);
                }

                Assert.IsTrue(true);
            }
        }
    }

という感じで利用できるはずである。この場合、とくにデータ型が Generics を利用している必要もないはず。
Converter<IEnumerator[], T> の実装は、適当に static method を用意すれば簡単なのだが、その部分を LCG によって作成してしまいましょう。
時間がなかったので、とりあえずイメージとしてはこんな感じになる、って程度です。
そもそも new T() を呼ぶとか書いてあるくせに GetGenericArguments() してたりしてるのは、もともと N 個のパラメータ型を取る型を作ろうとしていた頃の名残です。
GetInt32Incr() は標準ライブラリのどこかにありそうです。そうそう Perl6 では猫演算子も導入されるのでこのような列挙を遅延評価で表現することができるようになっています。

0..10 ==> grep($_ % 2 == 0) # 0, 1, 2, ..., 8, 9, 10
0^..10 ==> grep($_ % 2 == 0) # 1, 2, 3, ..., 8, 9, 10
0..^10 ==> grep($_ % 2 == 0) # 0, 1, 2, ..., 7, 8, 9
0^..^10 ==> grep($_ % 2 == 0) # 1, 2, 3, ..., 7, 8, 9
0 .. Inf ==> # 0, 1, 2, ..., 9, 10, 11, ...

みたいなかんじで記述して、値の列挙ができます。前後を含まない範囲指定が猫の顔文字に見えるから 猫演算子(Neko Operator) なんですね。


        /// <summary> new T(...) を呼び出す Converter を生成します。 </summary>
        public static Converter<IEnumerator[], T> GenerateConverter<T>(params IEnumerator[] enumerators)
        {
            #region // 引数の確認

            // null チェック
            if (enumerators == null) 
            {
                throw new ArgumentNullException("enumerators"); 
            }

            // <int, xxx, ...> の場合、先頭の int を省略することを許す
            Type[] args = typeof(T).GetGenericArguments();
            if ((args.Length + 1 == enumerators.Length) && (args[0] == typeof(int)))
            {
                IEnumerator[] enumsWithIndex = new IEnumerator[args.Length + 1];
                Array.Copy(enumerators, 0, enumsWithIndex, 1, enumerators.Length);

                enumsWithIndex[0] = GetInt32Incr();
                enumerators = enumsWithIndex;
            }

            // 可変長引数の数の確認
            if (args.Length != enumerators.Length)
            {
                throw new ArgumentOutOfRangeException("enumerators");
            }

            #endregion

            Type[] methodArgs = new Type[args.Length];
            for (int i = 0; i < methodArgs.Length; i++)
            {
                methodArgs[i] = typeof(IEnumerator);
            }

            DynamicMethod method = new DynamicMethod("Create" + typeof(T).Name, typeof(T), methodArgs, typeof(T));
            ILGenerator gen = method.GetILGenerator();

            // すべての enumerator から
            for (int i = 0; i < enumerators.Length; i++)
            {
                // IEnumerator e = enumerators[i]
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldc_I4, i);
                gen.Emit(OpCodes.Ldelem_Ref);

                Type ienumerable = enumerators.GetType().GetInterface(typeof(IEnumerator<>).FullName);
                if (ienumerable != null)
                {
                    // IEnumerator<T>.Current を取得
                    MethodInfo get_Current = ienumerable.GetProperty("Current").GetGetMethod();

                    // T o = e.Current;
                    gen.EmitCall(OpCodes.Callvirt, get_Current, null);

                    if (args[i].IsAssignableFrom(ienumerable.GetGenericArguments()[0]))
                    {
                        // IEnumerator<T> かつ代入互換性がある場合、なにもしない
                        continue;
                    }
                }
                else
                {
                    // object o = e.Current
                    gen.EmitCall(OpCodes.Callvirt, IEnumerator_get_Current, null);
                }

                // 代入互換性がないので変換を実施する。
                // 互換性がない型で列挙を実施すると、InvalidCastException が発生する
                // cf. foreach (int v in new string[] { ... } ) と同様
                gen.Emit(OpCodes.Unbox_Any, args[i]);
            }

            // Zipper z = new T(enumerator[0].Current, enumerator[1].Current,
            //                  (U) enumerator[2].Current, ...)
            gen.Emit(OpCodes.Newobj, typeof(T).GetConstructor(args));

            // return z;
            gen.Emit(OpCodes.Ret);

            return (Converter<IEnumerator[], T>) method.CreateDelegate(typeof(Converter<IEnumerator[], T>));
        }

        /// <summary> ZIP列挙用 MoveNext() ヘルパメソッド </summary>
        private static bool MoveNext(params IEnumerator[] enumerators)
        {
            bool result = true;

            foreach (IEnumerator e in enumerators)
            {
                result &= e.MoveNext();
            }

            return result;
        }

        /// <summary> indexed ループ用ヘルパ </summary>
        private static IEnumerator<int> GetInt32Incr()
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                yield return i;
            }

            yield return int.MaxValue;
        }

        // IEnumerator.Current.get
        private static MethodInfo IEnumerator_get_Current = typeof(IEnumerator).GetProperty("Current").GetGetMethod();