当然可以。使用标准查询运算符,虽然使用LINQ有点棘手,但确实可以做到。
更新:
我的博客于2010年6月28日星期一就此问题发表了文章,感谢这个好问题的提出者。此外,我博客上的评论者指出,有比我给出的更优雅的查询。我将在此处更新代码以使用它。
棘手的部分是使任意多个序列的笛卡尔积。与字母“zipping”相比,这很微不足道。您应该学习这个,以确保理解它的工作原理。每个部分都很简单,但它们如何组合在一起需要一些时间来适应:
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();
}
轻而易举!