かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

SelectManyの連打をお手軽にするユーテリティ


というつぶやきをみて、確かになぁと思ったけど、それでもあえて「メソッド構文で書きたい」という信念を貫きたい*1という人向け。
要は、値の組を後ろに伝搬させればいいということなのでTupleを使えばいい…!ということでSelectManyが一発ならこう書けます。

var values = Enumerable.Range(1, 10)
    .SelectMany(i => Enumerable.Range(0, i * i), Tuple.Create);

foreach (var i in values)
{
    Console.WriteLine("{0} {1}", i.Item1, i.Item2);
}

おっいい感じじゃね?ということでSelectManyを重ねるとちょっと残念になっちゃいます。

var values = Enumerable.Range(1, 10)
    .SelectMany(i => Enumerable.Range(0, i * i), Tuple.Create)
    .SelectMany(t => Enumerable.Range(0, t.Item2 * t.Item2), Tuple.Create);

foreach (var i in values)
{
    // あれ・・・値を辿るのがめんどくさい…
    Console.WriteLine("{0} {1} {2}", i.Item1.Item1, i.Item1.Item2, i.Item2);
}

ふむ、i.Item1.Item1とか嫌になりますよね。SelectManyを更に重ねていくとi.Item1.Item1.Item1.Item1....とか発狂しそうです。さっきのConsole.WriteLineの行は、こう書きたいです。

Console.WriteLine("{0} {1} {2}", i.Item1, i.Item2, i.Item3);

なら、Tupleと値を1つ受け取ってTupleを返すメソッドを定義すれば…

public static class TupleUtil
{
    public static Tuple<T1, T2, T3> Append<T1, T2, T3>(Tuple<T1, T2> t, T3 v)
    {
        return Tuple.Create(t.Item1, t.Item2, v);
    }
}

こんな風に書けます。

var values = Enumerable.Range(1, 10)
    .SelectMany(i => Enumerable.Range(0, i * i), Tuple.Create)
    .SelectMany(t => Enumerable.Range(0, t.Item2 * t.Item2), TupleUtil.Append);

foreach (var i in values)
{
    Console.WriteLine("{0} {1} {2}", i.Item1, i.Item2, i.Item3);
}

うん、いい感じ。ということで、BCLに定義されてるTupleの引数の最大値は8個なので8個まで淡々とオーバーロードを増やした結果こんなクラスができます。

public static class TupleUtil
{
    public static Tuple<T1, T2, T3> Append<T1, T2, T3>(Tuple<T1, T2> t, T3 v)
    {
        return Tuple.Create(t.Item1, t.Item2, v);
    }

    public static Tuple<T1, T2, T3, T4> Append<T1, T2, T3, T4>(Tuple<T1, T2, T3> t, T4 v)
    {
        return Tuple.Create(t.Item1, t.Item2, t.Item3, v);
    }

    public static Tuple<T1, T2, T3, T4, T5> Append<T1, T2, T3, T4, T5>(Tuple<T1, T2, T3, T4> t, T5 v)
    {
        return Tuple.Create(t.Item1, t.Item2, t.Item3, t.Item4, v);
    }

    public static Tuple<T1, T2, T3, T4, T5, T6> Append<T1, T2, T3, T4, T5, T6>(Tuple<T1, T2, T3, T4, T5> t, T6 v)
    {
        return Tuple.Create(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, v);
    }

    public static Tuple<T1, T2, T3, T4, T5, T6, T7> Append<T1, T2, T3, T4, T5, T6, T7>(Tuple<T1, T2, T3, T4, T5, T6> t, T7 v)
    {
        return Tuple.Create(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6, v);
    }

    public static Tuple<T1, T2, T3, T4, T5, T6, T7, T8> Append<T1, T2, T3, T4, T5, T6, T7, T8>(Tuple<T1, T2, T3, T4, T5, T6, T7> t, T8 v)
    {
        return new Tuple<T1, T2, T3, T4, T5, T6, T7, T8>(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6, t.Item7, v);
    }

}

こうしておくと、ここまでSelectManyを連打できます。

var values = Enumerable.Range(1, 10)
    .SelectMany(i => Enumerable.Range(0, i * i), Tuple.Create)
    .SelectMany(t => Enumerable.Range(0, t.Item2 * t.Item2), TupleUtil.Append)
    .SelectMany(t => Enumerable.Range(0, t.Item3 * t.Item3), TupleUtil.Append)
    .SelectMany(t => Enumerable.Range(0, t.Item4 * t.Item4), TupleUtil.Append)
    .SelectMany(t => Enumerable.Range(0, t.Item5 * t.Item5), TupleUtil.Append)
    .SelectMany(t => Enumerable.Range(0, t.Item6 * t.Item6), TupleUtil.Append)
    .SelectMany(t => Enumerable.Range(0, t.Item7 * t.Item7), TupleUtil.Append);

foreach (var i in values)
{
    Console.WriteLine("{0} {1} {2} {3}", i.Item1, i.Item2, i.Item3, i.Item4, i.Item5, i.Item6, i.Item7, i.Rest);
}

これ以上は…考えないようにしよう。

*1:特にメソッド構文のほうが優れてるとかそういうのを言いたいわけではなく、メソッド構文でも楽な書き方って無いかなと考えて思いついただけです