LINQでデカルト積を実現したい(Pythonのitertools#productのような機能を実現したい)


Pythonには`itertools#product`というデカルト積を簡単に実現できるメソッドが用意されており、個人的には2重ループのネストを減らすために頻繁に利用します。C#では標準ライブラリには用意されていないものの、以下のサンプルに示すように`SelectMany`を利用すれば、これと同様の機能が実現可能です。

```csharp
using System;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    public static IEnumerable<TResult> product<T1, T2, TResult>(IEnumerable<T1> ts1, IEnumerable<T2> ts2, Func<T1, T2, TResult> func)
    {
         return ts1.SelectMany((t1) => (ts2.Select((t2) => func.Invoke(t1, t2))));
    }

    public static IEnumerable<TResult> product<T1, T2, T3, TResult>(IEnumerable<T1> ts1, IEnumerable<T2> ts2, IEnumerable<T3> ts3, Func<T1, T2, T3, TResult> func)
    {
        return ts1.SelectMany((t1) => product(ts2, ts3, (t2, t3) => func.Invoke(t1, t2, t3)));
    }

    public static void Main()
    {
        var strs = new List<string>() { "a", "b", "c" };
        var nums = new List<int>() { 1, 2, 3 };

        // "べた"に書く場合:
        var tuples1 = strs.SelectMany((str) => nums.Select((num) => new Tuple<string, int>(str, num)));
        foreach (var tuple in tuples1)
        {
            Console.WriteLine(tuple);
        }

        // メソッド定義した場合: 
        var tuples2 = product(strs, nums, (str, num) => new Tuple<string, int>(str, num));
        foreach (var tuple in tuples2)
        {
            Console.WriteLine(tuple);
        }
        
        // 引数が3つの場合:
        var xs = new List<int>() { 1, 2, 3 };
        var ys = new List<int>() { 1, 2, 3 };
        var zs = new List<int>() { 1, 2, 3 };
        var tuples3 = product(xs, ys, zs, (x, y, z) => (new Tuple<int, int, int>(x, y, z)));
        foreach (var tuple in tuples3)
        {
            Console.WriteLine(tuple);
        }

        // ===> "べた"に書く場合 <===
        // (a, 1)
        // (a, 2)
        // (a, 3)
        // (b, 1)
        // (b, 2)
        // (b, 3)
        // (c, 1)
        // (c, 2)
        // (c, 3)
        
        // ===> メソッド定義した場合 <===
        // (a, 1)
        // (a, 2)
        // (a, 3)
        // (b, 1)
        // (b, 2)
        // (b, 3)
        // (c, 1)
        // (c, 2)
        // (c, 3)

        // ===> 引数が3つの場合 <===
        // (1, 1, 1)
        // (1, 1, 2)
        // (1, 1, 3)
        // (1, 2, 1)
        // (1, 2, 2)
        // (1, 2, 3)
        // (1, 3, 1)
        // (1, 3, 2)
        // (1, 3, 3)
        // (2, 1, 1)
        // (2, 1, 2)
        // (2, 1, 3)
        // (2, 2, 1)
        // (2, 2, 2)
        // (2, 2, 3)
        // (2, 3, 1)
        // (2, 3, 2)
        // (2, 3, 3)
        // (3, 1, 1)
        // (3, 1, 2)
        // (3, 1, 3)
        // (3, 2, 1)
        // (3, 2, 2)
        // (3, 2, 3)
        // (3, 3, 1)
        // (3, 3, 2)
        // (3, 3, 3)
    }
}
```

ちなみに`SelectMany`という名前、あまり耳慣れない類ですが、ほかのプログラミング言語でいうところの`flattenmap`や`flatmap`と同じものになります。個人的には`SelectMany`という名前は直観に反すると思うのですが、好みですかね(´・ω・｀)

***

とここまで書いたあと、ふと`yield return`という遅延評価を実現する構文があることを思い出しました(´・ω・｀)　これを利用するとよりシンプルかつ分かりやすい形でデカルト積が実現できてしまいます。まあ好みの問題ですね……。

```csharp
using System;
using System.Collections.Generic;

public class Program
{
    public static IEnumerable<TResult> product<T1, T2, TResult>(IEnumerable<T1> ts1, IEnumerable<T2> ts2, Func<T1, T2, TResult> func)
    {
        foreach (var t1 in ts1)
        {
            foreach (var t2 in ts2)
            {
                yield return func(t1, t2);
            }
        }
    }

    public static IEnumerable<TResult> product<T1, T2, T3, TResult>(IEnumerable<T1> ts1, IEnumerable<T2> ts2, IEnumerable<T3> ts3, Func<T1, T2, T3, TResult> func)
    {
        foreach (var t1 in ts1)
        {
            foreach (var t2 in ts2)
            {
                foreach (var t3 in ts3)
                {
                    yield return func(t1, t2, t3);
                }
            }
        }
    }
}
```