简介
你想做的事叫:cartesian product
在继续之前让我们做一些命名。我会将您的输入列表命名为L_i,其中1 <= i <= n。我还将S_i命名为输入列表的大小L_i。
我们可以提问:what is the size of the output ?
如果只有一个列表L_1,则将有S_1 输出列表,每个列表只包含L_1 的一个元素。
如果有两个列表{L_1, L_2}。对于L_1 的每个元素,我可以附加S_2 的L_2 的不同元素。由于L_1 有S_1 元素,它使S_1*S_2 产生不同的输出列表。
我们可以继续推理n列表,证明输出列表的数量为:S_1*...*S_n。
这对我们有什么帮助?因为我们现在可以在数字i 和输出列表之间创建映射。
给定i 一个数字0<=i<S_1*...*S_n,输出列表包含:
element of L_1 at index i/(S_2*S_3*...*S_n) MOD S_1
element of L_2 at index i/( S_3*...*S_n) MOD S_2
...
element of L_n at index i MOD S_n
实现示例
我不了解 VB.net,所以我选择了使用相同 .net 平台的 C#。
我决定使用yield return 函数,这样我们就不会分配比需要更多的内存。如果您只需要打印输出,它只会消耗单个 ulong 内存,而不是分配非常大的输出列表列表。
using System;
using System.Collections.Generic;
using System.Linq;
namespace cartesian_product
{
class Program
{
public static IEnumerable<List<T>> cartesian_product<T>(IEnumerable<List<T>> lists)
{
ulong MAX_I = lists.Select(l => (ulong)l.Count)
.Aggregate(1ul, (a, b) => a * b);
for (ulong i = 0; i < MAX_I; ++i)
{
var output = new List<T>();
ulong div = MAX_I;
ulong index, s_i;
foreach (var l in lists)
{
s_i = (ulong)l.Count;
div /= s_i;
index = (i/div)%s_i;
output.Add(l[(int)index]);
}
yield return output;
}
}
static void Main(string[] args)
{
var first = new List<Char> {'a', 'b', 'c', 'd'};
var second = new List<Char> {'e'};
var third = new List<Char> {'f', 'g', 'h', 'i', 'j'};
Console.WriteLine("{");
foreach (var output in cartesian_product(new[]{first, second, third}))
{
Console.WriteLine("{{{0}}}", string.Join(",", output));
}
Console.WriteLine("}");
}
}
}
输出是:
{
{a,e,f}
{a,e,g}
{a,e,h}
{a,e,i}
{a,e,j}
{b,e,f}
{b,e,g}
{b,e,h}
{b,e,i}
{b,e,j}
{c,e,f}
{c,e,g}
{c,e,h}
{c,e,i}
{c,e,j}
{d,e,f}
{d,e,g}
{d,e,h}
{d,e,i}
{d,e,j}
}
限制
有人可能会问:what if the product of the lists length overflows the variable used to index the outputs ?。
这是一个真正的理论问题,但我在我的代码中使用了ulong,如果输出列表的总数溢出这个变量,那么无论你使用什么方法,你都很难枚举输出。 (因为理论上的输出将包含多个2^64 列表)。
应用程序
OP 没有解释为什么他首先需要这个算法。所以读者可能想知道why is this useful ?。其中一个原因可能是为回归测试生成测试用例。假设您有一个将三个变量作为输入的遗留函数。您可以为每个参数生成一些可能的值,并为每个可能的参数集使用函数的笛卡尔积收集结果。重构遗留代码后,您可以通过比较新代码输出和遗留代码输出来确保没有回归。