关于C#:如何获得具有不同元素数的n个数组的所有可能组合?

How to get all possible combinations for n arrays with different number of elements?

我有一些数组在编程时是未知的,可能是3、4或7…每个数组都有一些元素,即,

1
2
3
4
5
a={1 2 3 4}
b={6 7 5 2 1}
c={22 4 6 8 4 8 5 4}
d={....}
e, f, g, ...

我想通过从每个数组中抽取一个数字来获得所有可能的组合,例如,我从A中选取"1",从B中选取"7",从C、D[3],E[5]中选取"8"…使"1,7,8,d[3],e[5],…"。不能使用嵌套for循环,因为我不知道编译时数组的数量。如果知道例如4个数组(A、B、C、D),我可以使用4个循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
for (int i = 0; i <= a.Length-1; i++)
{
   for (int j = 0; i <= b.Length-1; j++)
   {
      for (int k = 0; i <= c.Length-1; k++)
      {
         for (int m = 0; i <= d.Length-1; m++)
         {
            Response[f++] = a[i].toString()+","+b[j].toString()+","+c[k].toString()+","+d[m].toString();
         }
      }
   }
}

但是对于不同数量的数组,我不知道。


这工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Func<
    IEnumerable<IEnumerable<int>>,
    IEnumerable<IEnumerable<int>>> f0 = null;
f0 = xss =>
{
    if (!xss.Any())
    {
        return new [] { Enumerable.Empty<int>() };
    }
    else
    {
        var query =
            from x in xss.First()
            from y in f0(xss.Skip(1))
            select new [] { x }.Concat(y);
        return query;
    }
};

Func<IEnumerable<IEnumerable<int>>, IEnumerable<string>> f =
    xss => f0(xss).Select(xs => String.Join(",", xs));

所以如果我有这个输入:

1
2
3
4
5
6
var input = new []
{
    new [] { 1, 2, 3, 4, },
    new [] { 6, 7, 5, 2, 1, },
    new [] { 22, 4, 6, 8, 4, 8, 5, 4, },
};

我可以这样得到结果:

1
var results = f(input);

results

下面是一个版本,它根据注释中的请求简单地对结果进行汇总:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Func<IEnumerable<IEnumerable<int>>, IEnumerable<int>> f = null;
f = xss =>
{
    if (!xss.Any())
    {
        return new [] { 0 };
    }
    else
    {
        var query =
            from x in xss.First()
            from y in f(xss.Skip(1))
            select x + y;
        return query;
    }
};

var input = new []
{
    new [] { 1, 2, 3, 4, },
    new [] { 6, 7, 5, 2, 1, },
    new [] { 22, 4, 6, 8, 4, 8, 5, 4, },
};

var results = f(input);


以下扩展方法模拟foreach循环的嵌套堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static class Ext
{
    public static void ForEachNested<T>(
        this IEnumerable<IEnumerable<T>> sources,
        Action<IEnumerable<T>> action)
    {
        var enumerables = sources.ToArray();
        Stack<IEnumerator<T>> fe = new Stack<IEnumerator<T>>();
        fe.Push(enumerables[0].GetEnumerator());

        while (fe.Count > 0)
        {
            if (fe.Peek().MoveNext())
            {
                if (fe.Count == enumerables.Length)
                    action(new Stack<T>(fe.Select(e => e.Current)));
                else
                    fe.Push(enumerables[fe.Count].GetEnumerator());
            }
            else
            {
                fe.Pop().Dispose();
            }
        }
    }
}

您可以使用它如下:

1
new[] { a, b, c, d }.ForEachNested(e => { Response[f++] = string.Join(",", e); });

或者,做你的数学,

1
new[] { a, b, c, d }.ForEachNested(e => { Response[f++] = e.Sum(); });

这样做的好处是不执行数百个阵列分配。

通常,我会避免使用类似LINQ的方法来执行操作,而不是使用查询,但由于这是在密切地模仿foreach循环的工作方式,所以我不介意这么多。


我觉得Linq版本看起来很棒:

1
2
3
4
5
from i in a
from j in b
from k in c
from m in d
select String.Join(",", i, j, k, m)

但你的问题的答案并不容易。埃里克·利珀特在他的博客上写到:

http://blogs.msdn.com/b/ericlippet/archive/2010/06/28/computing-a-cartesian-product-with-linq.aspx