読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

世界のナベアツ問題でExpression Tree(式木)と戯れる

プログラミング LINQ C#3.0

LINQの本質とはあんまり関係ないです。Expression Treeを使う練習です。
意味もなく汎用世界のナベアツ再びであります。
難しいことはしていません。生暖かい目で見守ってください(^ω^;)

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;

namespace ConsoleApplication1
{
    static class Program
    {
        static void Main(string[] args)
        {
            var nums = Enumerable.Range(1, 40);

            Console.WriteLine("#世界のナベアツです。1から40まで数えます。");
            Console.WriteLine("#○の倍数と、○の付く数字だけ●になります。");
            Console.WriteLine("#○にあたる数値を入力してください。");
            var input1 = Console.ReadLine();
            int hoge1 = default(int);
            if (!int.TryParse(input1, out hoge1)) return;
            if (hoge1 > nums.Count()) return;

            Console.WriteLine("#●にあたる文字列を入力してください。");
            var hoge2 = Console.ReadLine();

            Console.WriteLine("#さらに、□の倍数のとき■になります。");
            Console.WriteLine("#□にあたる数値を入力してください。");
            var input3 = Console.ReadLine();
            int pugera1 = default(int);
            if (!int.TryParse(input3, out pugera1)) return;
            if (hoge1 == pugera1 || pugera1 > nums.Count()) return;

            Console.WriteLine("#■にあたる文字列を入力してください。");
            var pugera2 = Console.ReadLine();

            // ほげの倍数
            Expression<Func<int, bool>> hogeBai = n => n % hoge1 == 0;
            // ほげの付く数字
            Expression<Func<int, bool>> hogeTuku = n => n.ToString().IndexOf(hoge1.ToString()) != -1;
            // いずれかのほげを満たしたらな式木
            var hogeLambda = hogeBai.Or(hogeTuku);
            // ぷげらの倍数
            Expression<Func<int, bool>> pugeraLambda = n => n % pugera1 == 0;

            // ほげのみ
            var hogeonly = hogeLambda.And(Not(pugeraLambda));
            // ぷげらのみ
            var pugeraonly = pugeraLambda.And(Not(hogeLambda));
            // ほげ且つぷげら
            var hogepugera = hogeLambda.And(pugeraLambda);

            Console.WriteLine("↓ナベアツの結果");

            nums.Select(
                n =>{var s = n.ToString();
                     if (hogepugera.Compile()(n)) return s + hoge2 + pugera2;
                     if (pugeraonly.Compile()(n)) return s + pugera2;
                     if (hogeonly.Compile()(n)) return s + hoge2;
                     return s;}
                ).ForEach(Console.WriteLine);

            Console.ReadLine();
        }

        static void ForEach<T>(this IEnumerable<T> src, Action<T> action)
        {
            foreach (T item in src) { action(item); }
        }

        static Expression<Func<T, TResult>> Not<T, TResult>(Expression<Func<T, TResult>> lambda)
        {
            var param = lambda.Parameters.ToArray();
            var not = Expression.Not(lambda.Body);
            return Expression.Lambda<Func<T, TResult>>(not, param);
        }

        static Expression<Func<T, TResult>> Or<T, TResult>(this Expression<Func<T, TResult>> lhs, Expression<Func<T, TResult>> rhs)
        {
            var param = lhs.Parameters.ToArray();
            var invoke = Expression.Invoke(rhs, param);
            var or = Expression.Or(lhs.Body, invoke);
            return Expression.Lambda<Func<T, TResult>>(or, param);
        }

        static Expression<Func<T, TResult>> And<T, TResult>(this Expression<Func<T, TResult>> lhs, Expression<Func<T, TResult>> rhs)
        {
            var param = lhs.Parameters.ToArray();
            var invoke = Expression.Invoke(rhs, param);
            var and = Expression.And(lhs.Body, invoke);
            return Expression.Lambda<Func<T, TResult>>(and, param);
        }
    }
}


Expression Tree(式木)のオモローなところは、制約*1こそあるもののラムダ式をデータとして扱えること。
そして、その式木をいろいろ弄繰り回した後に、その弄くり回した結果を好きなときにコンパイルして実行できちゃうところ。
つまり、式木を使えば、動的にクエリ式を作ったりも自由自在(?)ってゆー。


ただ・・・、式木を多用するには、ラムダ式(を読み書きすること)に違和感を感じない、
いわゆる「ラムダ脳(関数型言語脳)」が必要になってくる。
そのあたりがネックとなり、まともなIT技術者が不足している札幌での適用は難しいかもしれない*2
というか、アレです。いつ仕事でC#3.0が弄れるようになるの?>誰に聞いている

*1:単文のラムダ式のみ式木にすることが可能

*2:ラムダ式はわかり難いから使っちゃだめ」みたいな規約とか作られそう・・・。それマジ勘弁^^;