かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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