如何在N个可变长度的vb列表中生成值的所有组合?
假设我有N个vb列表,例如
first = {'a', 'b', 'c', 'd'}
second = {'e'}
third = {'f', 'g', 'h', 'i', 'j'}
(在这个例子中有三个列表,但它有N个问题列表。)
我想输出其值的所有组合,以生成订单中的列表列表。
{
{a,e,f}
{a,e,g}
{a,e,h}
{a,e,i}
{a,e,j}
{b,e,f}
{b,e,g}
....
{d,e,j}
}
答案 0 :(得分:3)
使用python实现的简单方法
import itertools
first = ['a', 'b', 'c', 'd']
second = ['e']
third = ['f', 'g', 'h', 'i', 'j']
for x in itertools.product (first, second, third):
print x
答案 1 :(得分:3)
<强>简介强>
您想要做的是: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
的不同元素。由于S_1
有L_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 ?
。其中一个原因可能是为回归测试生成测试用例。假设你有一个遗留函数作为输入三个变量。您可以为每个参数生成一些可能的值,并为每个可能的参数集使用函数的笛卡尔积收集结果。在重构遗留代码之后,您可以通过比较新代码输出和遗留代码输出来确保没有回归。
答案 2 :(得分:2)
这是一个组合问题,而不是排列问题。我们想要3个元素的所有组合,每个元素取自一组。订单由集合驱动,而不是元素。组合总数是集合计数的乘积。在这个例子中,那将是4 x 1 x 5 = 20.因为我们不知道有多少列表(称之为N)。我们知道N提前了,这很容易。我们可以编写一些嵌套循环来生成组合。不知道是什么让这个变得棘手。递归可能是解决它的最优雅方式。
Private Function AppendCombinations(Combinations As List(Of List(Of String)), Lists As List(Of List(Of String))) As List(Of List(Of String))
If Combinations Is Nothing Then
Combinations = New List(Of List(Of String))
For Each s As String In Lists.First
Dim newCombo As New List(Of String)
newCombo.Add(s)
Combinations.Add(newCombo)
Next
Dim newList As New List(Of List(Of String))
newList.AddRange(Lists)
newList.RemoveAt(0)
Return AppendCombinations(Combinations, newList)
Else
Dim newCombinations As New List(Of List(Of String))
For Each combo In Combinations
For Each s As String In Lists.First
Dim newCombo As New List(Of String)
newCombo.AddRange(combo)
newCombo.Add(s)
newCombinations.Add(newCombo)
Next
Next
Dim newList As New List(Of List(Of String))
newList.AddRange(Lists)
newList.RemoveAt(0)
If newList.Count > 0 Then
Return AppendCombinations(newCombinations, newList)
Else
Return newCombinations
End If
End If
End Function
可以按如下方式调用此函数。此示例假定列表是另一个名为 lists 的列表的成员。
Dim combinations As List(Of List(Of String))
combinations = AppendCombinations(combinations, lists)
答案 3 :(得分:1)
这是一种相当简单的做法(即没有Linq)。
假设一个带有Button和ListBox的表单。
为了简单起见,将所有内容存储在列表中:
Private listOfLists As New List(Of List(Of String))
'Something to keep track of where we are...
Private permCount As New List(Of Integer)
第二个列表就是通过排列来跟踪进度。
加载数据......
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
listOfLists.Add(New List(Of String)({"a", "b", "c", "d"}))
listOfLists.Add(New List(Of String)({"e"}))
listOfLists.Add(New List(Of String)({"f", "g", "h", "i", "j"}))
For i As Integer = 0 To listOfLists.Count - 1
permCount.Add(0)
Next
End Sub
其余的......
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
EnumeratePermutations()
End Sub
Private Sub EnumeratePermutations()
'ideally, reset permCount and ListBox1
Dim blnFinished As Boolean
Do Until blnFinished
WritePerm()
blnFinished = GetNext()
Loop
End Sub
Private Sub WritePerm()
Dim strPerm As String = ""
For i As Integer = 0 To listOfLists.Count - 1
strPerm += listOfLists(i)(permCount(i))
Next
ListBox1.Items.Add(strPerm)
End Sub
Private Function GetNext() As Boolean
For i As Integer = listOfLists.Count - 1 To 0 Step -1
'Increment if we can otherwise reset and move to previous list
If permCount(i) < listOfLists(i).Count - 1 Then
permCount(i) += 1
Return False
Else
permCount(i) = 0
End If
Next
'If we got here, then we ran out of permutations
Return True
End Function