生成所有可能的组合

时间:2010-06-22 13:39:14

标签: c# combinatorics cartesian-product

给定2个数组Array1 = {a,b,c...n}Array2 = {10,20,15....x}如何生成所有可能的组合作为字符串 a(i)b(j)c(k)n(p)

1 <= i <= 10,  1 <= j <= 20 , 1 <= k <= 15,  .... 1 <= p <= x

如:

a1 b1 c1 .... n1  
a1 b1 c1..... n2  
......  
......  
a10 b20 c15 nx (last combination)

因此,所有组合总数= array2 = (10 X 20 X 15 X ..X x)

元素的乘积

类似于笛卡尔积,其中第二个数组定义第一个数组中每个元素的上限。

固定数字的示例,

    Array x =  [a,b,c]
    Array y =  [3,2,4] 

所以我们将有3 * 2 * 4 = 24种组合。结果应该是:

    a1 b1 c1  
    a1 b1 c2  
    a1 b1 c3  
    a1 b1 c4  

    a1 b2 c1  
    a1 b2 c2  
    a1 b2 c3  
    a1 b2 c4


    a2 b1 c1  
    a2 b1 c2  
    a2 b1 c3  
    a2 b1 c4  

    a2 b2 c1  
    a2 b2 c2  
    a2 b2 c3  
    a2 b2 c4


    a3 b1 c1  
    a3 b1 c2  
    a3 b1 c3  
    a3 b1 c4  

    a3 b2 c1  
    a3 b2 c2  
    a3 b2 c3  
    a3 b2 c4 (last)

14 个答案:

答案 0 :(得分:144)

当然可以。使用LINQ执行此操作有点棘手但当然只能使用标准查询运算符。

更新:这是my blog on Monday June 28th 2010的主题;谢谢你提出的好问题。另外,我博客上的一位评论者指出,有一个比我给出的更优雅的查询。我会在这里更新代码以使用它。

棘手的部分是制作任意多个序列的笛卡尔积。与之相比,字母中的“压缩”是微不足道的。你应该研究这个,以确保你了解它是如何工作的。每个部分都很简单,但它们组合在一起的方式需要一些习惯:

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>()};
    return sequences.Aggregate(
        emptyProduct,
        (accumulator, sequence) => 
            from accseq in accumulator 
            from item in sequence 
            select accseq.Concat(new[] {item})                          
        );
 }

要解释这是如何工作的,首先要了解“累积”操作正在做什么。最简单的累积操作是“将所有序列加在一起”。你这样做的方式是:从零开始。对于序列中的每个项目,累加器的当前值等于累加器的项目和先前值的总和。我们做的是同样的事情,除了不是基于到目前为止的总和和当前项目累积总和,我们正在积累笛卡尔积。

我们要这样做的方法是利用我们已经在LINQ中有一个运算符来计算笛卡尔乘积的事实:

from x in xs
from y in ys
do something with each possible (x, y)

通过反复输入累加器的笛卡尔积和输入序列中的下一项并对结果进行一些粘贴,我们就可以生成笛卡尔积。

所以想想累加器的价值。为了便于说明,我将把累加器的值显示为它包含的序列运算符的结果。这不是累加器实际包含的内容。累加器实际包含的是生成这些结果的运算符。这里的整个操作只是建立了序列运算符的大量树,其结果是笛卡尔积。但是最终的笛卡尔积产品本身并不会在查询执行之前实际计算出来。为了说明的目的,我将展示结果在每个阶段的方式,但请记住,这实际上包含产生这些结果的运算符

假设我们正在采用序列序列{{1, 2}, {3, 4}, {5, 6}}的笛卡尔积。累加器以包含一个空序列的序列开始:{ { } }

在第一次累积时,累加器为{{}},项目为{1,2}。我们这样做:

from accseq in accumulator
from item in sequence 
select accseq.Concat(new[] {item})

所以我们将{ { } }的笛卡尔积与{1, 2}一起使用,对于每一对,我们连接:我们有({ }, 1)对,所以我们连接{ }和得到{1} {1}。我们有({ }, 2})对,因此我们将{ }{2}连接起来以获得{2}。因此,我们将{{1}, {2}}作为结果。

因此,在第二次累积时,累加器为{{1}, {2}},项目为{3, 4}。再次,我们计算这两个序列的笛卡尔乘积得到:

 {({1}, 3), ({1}, 4), ({2}, 3), ({2}, 4)}

然后从这些项目中将第二个连接到第一个项目上。结果是序列{{1, 3}, {1, 4}, {2, 3}, {2, 4}},这就是我们想要的。

现在我们再次积累。我们将累加器的笛卡尔积与{5, 6}得到

 {({ 1, 3}, 5), ({1, 3}, 6), ({1, 4}, 5), ...

然后将第二个项目连接到第一个项目来获取:

{{1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6} ... }

我们已经完成了。我们已经积累了笛卡尔积。

既然我们有一个实用函数可以采用任意多个序列的笛卡尔积,其余的比较容易:

var arr1 = new[] {"a", "b", "c"};
var arr2 = new[] { 3, 2, 4 };
var result = from cpLine in CartesianProduct(
                 from count in arr2 select Enumerable.Range(1, count)) 
             select cpLine.Zip(arr1, (x1, x2) => x2 + x1);

现在我们有一系列字符串序列,每行一个字符串序列:

foreach (var line in result)
{
    foreach (var s in line)
        Console.Write(s);
    Console.WriteLine();
}

轻松自在!

答案 1 :(得分:20)

using System;
using System.Text;

public static string[] GenerateCombinations(string[] Array1, int[] Array2)
{
    if(Array1 == null) throw new ArgumentNullException("Array1");
    if(Array2 == null) throw new ArgumentNullException("Array2");
    if(Array1.Length != Array2.Length)
        throw new ArgumentException("Must be the same size as Array1.", "Array2");

    if(Array1.Length == 0)
        return new string[0];

    int outputSize = 1;
    var current = new int[Array1.Length];
    for(int i = 0; i < current.Length; ++i)
    {
        if(Array2[i] < 1)
            throw new ArgumentException("Contains invalid values.", "Array2");
        if(Array1[i] == null)
            throw new ArgumentException("Contains null values.", "Array1");
        outputSize *= Array2[i];
        current[i] = 1;
    }

    var result = new string[outputSize];
    for(int i = 0; i < outputSize; ++i)
    {
        var sb = new StringBuilder();
        for(int j = 0; j < current.Length; ++j)
        {
            sb.Append(Array1[j]);
            sb.Append(current[j].ToString());
            if(j != current.Length - 1)
                sb.Append(' ');
        }
        result[i] = sb.ToString();
        int incrementIndex = current.Length - 1;
        while(incrementIndex >= 0 && current[incrementIndex] == Array2[incrementIndex])
        {
                current[incrementIndex] = 1;
                --incrementIndex;
        }
        if(incrementIndex >= 0)
            ++current[incrementIndex];
    }
    return result;
}

答案 2 :(得分:13)

替代解决方案:

第一步:阅读我关于如何生成与上下文敏感语法匹配的所有字符串的系列文章:

http://blogs.msdn.com/b/ericlippert/archive/tags/grammars/

第二步:定义一个生成所需语言的语法。例如,您可以定义语法:

S: a A b B c C
A: 1 | 2 | 3
B: 1 | 2
C: 1 | 2 | 3 | 4

显然,您可以轻松地从两个数组生成语法定义字符串。然后将其提供给代码,该代码生成给定语法中的所有字符串,然后就完成了;你将获得所有可能性。 (请注意,不一定按照你想要的顺序。)

答案 3 :(得分:3)

为了比较,这是一种使用Python

的方法
from itertools import product
X=["a", "b", "c"]
Y=[3, 4, 2]
terms = (["%s%s"%(x,i+1) for i in range(y)] for x,y in zip(X,Y))
for item in product(*terms):
    print " ".join(item)

答案 4 :(得分:2)

Fon另一种解决方案不是linq,你可以使用:

public class CartesianProduct<T>
    {
        int[] lengths;
        T[][] arrays;
        public CartesianProduct(params  T[][] arrays)
        {
            lengths = arrays.Select(k => k.Length).ToArray();
            if (lengths.Any(l => l == 0))
                throw new ArgumentException("Zero lenght array unhandled.");
            this.arrays = arrays;
        }
        public IEnumerable<T[]> Get()
        {
            int[] walk = new int[arrays.Length];
            int x = 0;
            yield return walk.Select(k => arrays[x++][k]).ToArray();
            while (Next(walk))
            {
                x = 0;
                yield return walk.Select(k => arrays[x++][k]).ToArray();
            }

        }
        private bool Next(int[] walk)
        {
            int whoIncrement = 0;
            while (whoIncrement < walk.Length)
            {
                if (walk[whoIncrement] < lengths[whoIncrement] - 1)
                {
                    walk[whoIncrement]++;
                    return true;
                }
                else
                {
                    walk[whoIncrement] = 0;
                    whoIncrement++;
                }
            }
            return false;
        }
    }

您可以在how to use it here上找到一个示例。

答案 5 :(得分:1)

我不愿意给你完整的源代码。所以这就是背后的想法。

您可以通过以下方式生成元素:

我假设A=(a1, a2, ..., an)B=(b1, b2, ..., bn)(因此AB各占n个元素。)

然后递归地做!写一个方法,它需要AB并做你的事情:

如果AB每个只包含一个元素(称为an resp。bn),则只需从1迭代到bn并连接{{ 1}}到你的迭代变量。

如果anA每个都包含多个元素,请抓取第一个元素(B resp a1),从1迭代到b1,为每个迭代步骤做:

  • bnA的子字段开始递归调用该方法,从第二个元素开始,即B resp A'=(a2, a3, ..., an)。对于递归调用生成的每个元素,连接B'=(b2, b3, ..., bn),迭代变量和递归调用中生成的元素。

Here你可以找到一个如何用C#生成东西的例子,你只需要根据自己的需要调整它。

答案 6 :(得分:1)

如果我做得对,你就是 Cartesian product 。 如果是这种情况,您可以使用LINQ来完成此操作。可能不是确切的答案,但试图得到这个想法


    char[] Array1 = { 'a', 'b', 'c' };
    string[] Array2 = { "10", "20", "15" };

    var result = from i in Array1
                 from j in Array2
                   select i + j;

这些文章可能会有所帮助

答案 7 :(得分:1)

finalResult是所需的数组。假设两个阵列的大小相同。

char[] Array1 = { 'a', 'b', 'c' };
int[] Array2 = { 3, 2, 4 };

var finalResult = new List<string>();
finalResult.Add(String.Empty);
for(int i=0; i<Array1.Length; i++)
{
    var tmp = from a in finalResult
              from b in Enumerable.Range(1,Array2[i])
              select String.Format("{0} {1}{2}",a,Array1[i],b).Trim();
    finalResult = tmp.ToList();
}

我认为这就足够了。

答案 8 :(得分:1)

使用.NET Framework 4.7.1中添加的Enumerable.Append,可以实现@EricLippert的答案,而无需在每次迭代时分配新的数组:

public static IEnumerable<IEnumerable<T>> CartesianProduct<T>
    (this IEnumerable<IEnumerable<T>> enumerables)
{
    IEnumerable<IEnumerable<T>> Seed() { yield return Enumerable.Empty<T>(); }

    return enumerables.Aggregate(Seed(), (accumulator, enumerable)
        => accumulator.SelectMany(x => enumerable.Select(x.Append)));
}

答案 9 :(得分:0)

这是一个javascript版本,我相信有人可以转换。它经过了彻底的测试。

Here's the fiddle

function combinations (Asource){

    var combos = [];
    var temp = [];

    var picker = function (arr, temp_string, collect) {
        if (temp_string.length) {
           collect.push(temp_string);
        }

        for (var i=0; i<arr.length; i++) {
            var arrcopy = arr.slice(0, arr.length);
            var elem = arrcopy.splice(i, 1);

            if (arrcopy.length > 0) {
                picker(arrcopy, temp_string.concat(elem), collect);
            } else {
                collect.push(temp_string.concat(elem));
            }   
        }   
    }

    picker(Asource, temp, combos);

    return combos;

}

var todo = ["a", "b", "c", "d"]; // 5 in this set
var resultingCombos = combinations (todo);
console.log(resultingCombos);

答案 10 :(得分:0)

Fon另一个解决方案不是基于linq,更有效:

static IEnumerable<T[]> CartesianProduct<T>(T[][] arrays) {
    int[] lengths;
    lengths = arrays.Select(a => a.Length).ToArray();
    int Len = arrays.Length;
    int[] inds = new int[Len];
    int Len1 = Len - 1;
    while (inds[0] != lengths[0]) {
        T[] res = new T[Len];
        for (int i = 0; i != Len; i++) {
            res[i] = arrays[i][inds[i]];
        }
        yield return res;
        int j = Len1;
        inds[j]++;
        while (j > 0 && inds[j] == lengths[j]) {
            inds[j--] = 0;
            inds[j]++;
        }
    }
}

答案 11 :(得分:0)

那怎么样?

template<typename T>
std::vector<std::vector<T>> combinationOfVector(std::vector<T> vector,int nOfElem)
{
   std::vector<int> boleamRepresentationOfCombinations(vector.size(),0);
   for(auto it = boleamRepresentationOfCombinations.end()-nOfElem;it!=boleamRepresentationOfCombinations.end();it++)
   {
       *it=1;
   }
//   std::vector<std::vector<T>> toReturn(static_cast<int>(boost::math::binomial_coefficient<double>(vector.size()-1, nOfElem)));
//doenst work properly :c
    std::vector<std::vector<T>> toReturn;

   if(!std::is_sorted(boleamRepresentationOfCombinations.begin(),boleamRepresentationOfCombinations.end()))
       std::sort(boleamRepresentationOfCombinations.begin(),boleamRepresentationOfCombinations.end());
   do{
       std::vector<T> combination;
       combination.reserve(nOfElem);
       for(int i=0;i<boleamRepresentationOfCombinations.size();i++)
       {
            if(boleamRepresentationOfCombinations[i])
                combination.push_back(vector[i]);
       }
       toReturn.push_back(std::move(combination));
   }while(std::next_permutation(boleamRepresentationOfCombinations.begin(),boleamRepresentationOfCombinations.end()));
   return toReturn;
}

答案 12 :(得分:0)

如果有人对笛卡尔积算法的工业化、经过测试和受支持的实现感兴趣,欢迎您使用现成的 Gapotchenko.FX.Math.Combinatorics NuGet 包。

它提供了两种操作模式。基于 LINQ 的流畅模式:

using Gapotchenko.FX.Math.Combinatorics;
using System;

foreach (var i in new[] { "1", "2" }.CrossJoin(new[] { "A", "B", "C" }))
    Console.WriteLine(string.Join(" ", i));

还有一个更详细的显式模式:

using Gapotchenko.FX.Math.Combinatorics;
using System;

var seq1 = new[] { "1", "2" };
var seq2 = new[] { "A", "B", "C" };

foreach (var i in CartesianProduct.Of(seq1, seq2))
    Console.WriteLine(string.Join(" ", i));

两种模式产生相同的结果:

1 A
2 A
1 B
2 B
1 C
2 C

但它比这更进一步。例如,对 ValueTuple 结果的投影是一个简单的单行:

var results = new[] { 1, 2 }.CrossJoin(new[] { "A", "B" }, ValueTuple.Create);

foreach (var (a, b) in results)
  Console.WriteLine("{0} {1}", a, b);

结果的唯一性可以通过自然的方式实现:

var results = new[] { 1, 1, 2 }.CrossJoin(new[] { "A", "B", "A" }).Distinct();

乍一看,这种方法会产生过多的组合浪费。所以,而不是做

new[] { 1, 1, 2 }.CrossJoin(new[] { "A", "B", "A" }).Distinct()

在执行昂贵的乘法之前Distinct()序列可能更有益:

new[] { 1, 1, 2 }.Distinct().CrossJoin(new[] { "A", "B", "A" }.Distinct())

该软件包提供了一个自动计划构建器,可以优化掉这些特性。因此,两种方法具有相同的计算复杂度。

包的相应源代码比代码片段所能包含的要大一些,但可在 GitHub 获得。

答案 13 :(得分:-1)

我刚发现这个CodeProject帖子包含一个Facets.Combinatorics命名空间,其中包含一些有用的代码来处理C#中的Permuations,Combinations和Variations。

http://www.codeproject.com/Articles/26050/Permutations-Combinations-and-Variations-using-C-G