生成一个n-ary Cartesian产品示例

时间:2012-11-30 15:00:38

标签: c# .net linq cartesian-product

我发现Eric Lippert的帖子here适合我遇到的特定问题。

问题是我无法理解我应该如何使用2个以上的集合。

具有

var collections = new List<List<MyType>>();
foreach(var item in somequery)
{
    collections.Add(
            new List<MyType> { new MyType { Id = 1} .. n }
        );
}

如何在集合变量上应用笛卡尔积linq查询?

扩展方法就是这个:

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})                       
        );
 }

这是Eric的2个集合的例子:

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);

3 个答案:

答案 0 :(得分:5)

示例代码已经能够在“笛卡尔”产品中执行(示例中为3)。您的问题是,当您需要List<List<MyType>>

时,您有IEnumerable<IEnumerable<MyType>>
IEnumerable<IEnumerable<MyType>> result = collections
  .Select(list => list.AsEnumerable())
  .CartesianProduct();

答案 1 :(得分:0)

由于List<T>IEnumerable<T>,因此使用Eric解决方案的问题解决方法如下:

var collections = new List<List<MyType>>();
var product =  collections.CartesianProduct();
foreach(var collection in product)
{
    // a single collection of MyType items
    foreach(var item in collection)
    {
        // each item of type MyType within a collection
        Console.Write(item);
    }    
}

当然,您可以更简洁的方式聚合每个集合中的项目,例如单个string

var product = 
    collections
    .CartesianProduct()
    .Select(xs => xs.Aggregate(new StringBuilder(), (sb, x) => sb.Append(x.ToString()), sb => sb.ToString()));

foreach(var collectionAsString in product)
{
    Console.WriteLine(collectionAsString);
}

答案 2 :(得分:0)

很像这个问题的原始海报,我也很难理解这个很棒的功能的用法。吸引我的主要事情是我必须在调用该函数之前创建这个包含 IEnumerable 的单个 IEnumerable(再次就像原来的帖子一样)。

我的代码的设置方式是我有 3 个数组,其中包含需要相乘的数据,而创建这个更大的 IEnumerable 是我的方式,我不想这样做。

因此,我重写了函数以扩展 IEnumerable<T> 而不是 IEnumerable<IEnumerable<T>>,所以现在我可以直接从我想要相乘的任何数组中调用该函数并传入其他 2 个数组作为参数。我想我会在这里重新发布以防其他人有兴趣以这种方式使用它:

<块引用>
    public static IEnumerable<IEnumerable<T>> CartesianProduct<T>
    (this IEnumerable<T> firstSequence, params IEnumerable<T>[] sequences)
    {
        IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() };

        foreach (IEnumerable<T> sequence in (new[] { firstSequence }).Concat(sequences))
        {
            result = from resultItem in result 
                     from sequenceItem in sequence 
                     select resultItem.Concat(new[] { sequenceItem });
        }
        return result;
    }

这是在 3 个数据数组上使用此方法的示例。

<块引用>
        var numbers = new object[] { 1, 2, 3 };
        var letters = new object[] { "a", "b", "c" };
        var colors = new object[] { "red", "blue", "yellow" };

        var result = numbers.CartesianProduct(letters, colors);

输出

<块引用>
        [1, a, red]
        [1, a, blue]
        [1, a, yellow]
        [1, b, red]
        [1, b, blue]
        [1, b, yellow]
        [1, c, red]
        [1, c, blue]
        [1, c, yellow]
        [2, a, red]
        [2, a, blue]
        [2, a, yellow]
        [2, b, red]
        [2, b, blue]
        [2, b, yellow]
        [2, c, red]
        [2, c, blue]
        [2, c, yellow]
        [3, a, red]
        [3, a, blue]
        [3, a, yellow]
        [3, b, red]
        [3, b, blue]
        [3, b, yellow]
        [3, c, red]
        [3, c, blue]
        [3, c, yellow]