在 vb.net 中查找列表值的所有组合(笛卡尔积)

Posted

技术标签:

【中文标题】在 vb.net 中查找列表值的所有组合(笛卡尔积)【英文标题】:Finding All Combinations (cartesian product) of list values in vb.net 【发布时间】:2015-10-21 12:42:17 【问题描述】:

如何生成 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

【问题讨论】:

你要谷歌的词是permutations @Plutonix 你在哪里看到permutation?顺序已经确定了,并不是所有的元素都涉及... 这是一个嵌套循环或递归解决方案 这里似乎有一个很好的例子:***.com/questions/27328235/… 尽管是 c# 或这里:***.com/questions/545703/combination-of-listlistint @fjardon 您似乎没有正确理解您发布的链接中的定义。 “将集合的所有成员排列成某种顺序或顺序”还包括起始顺序(以及它的任何变体)。因此,始终根据您发布的定义,任何与顺序相关的元素集都可以称为排列,而不是顺序无关紧要的组合。在这里(与任何基于集合的编程情况一样)顺序确实很重要,因此排列是正确的名称...... 【参考方案1】:

简介

你想做的事叫: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_2L_2 的不同元素。由于L_1S_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】:

用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

【讨论】:

通过将此问题理解为请求通用算法(根本与 VB.NET 无关),您不应使用特定于语言的方法进行回复(除非是 VB.NET 逻辑上的方法) .另一方面,如果不是 Python 专家,您似乎 (docs.python.org/2/library/itertools.html) 不能以这种方式使用 intertools.product。事实上,有一个 SO question 询问仅 1 个列表(不太复杂)的所有可能排列,***.com/questions/104420/… 并且没有答案提供这个。 我不知道 Python 以及这个示例是否可行,但如果可行,那么这是一个令人印象深刻的功能。 Up 1.这确实是python中的正确解决方案,我会去的。但缺点是它没有解释 如何 用另一种语言来做到这一点:)。 (是的,python 很棒)【参考方案3】:

这是一个组合问题,而不是排列问题。我们想要 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)

【讨论】:

【参考方案4】:

这是一种相当简单的 in 方法(即没有 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

【讨论】:

以上是关于在 vb.net 中查找列表值的所有组合(笛卡尔积)的主要内容,如果未能解决你的问题,请参考以下文章

关系数据库

笛卡尔积

Mysql笛卡尔积详解(附实现多表查询代码实现)

有没有一种很好的 LINQ 方法来做笛卡尔积?

N 个数组的笛卡尔积

Erlang List对笛卡尔积的理解