肯定的事。使用 LINQ 执行此操作有点棘手,但肯定可以仅使用标准查询运算符。
更新:这是我 2010 年 6 月 28 日星期一博客的主题;谢谢你的好问题。此外,我博客上的一位评论者指出,有一个比我给出的更优雅的查询。我将在此处更新代码以使用它。
棘手的部分是制作任意多个序列的笛卡尔积。与此相比,字母中的“压缩”是微不足道的。你应该研究这个以确保你理解它是如何工作的。每个部分都很简单,但它们组合在一起的方式需要一些时间来适应:
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})
所以我们取{ { } }
with的笛卡尔积{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();
}
十分简单!