2

How can I produce all of the combinations of the values in N number of vb list of variable lengths?

Let's say I have N number of vb lists, e.g.

first = {'a', 'b', 'c', 'd'}
second = {'e'}
third =  {'f', 'g', 'h', 'i', 'j'}

(Three list in this example, but its N number of lists for the problem.)

And I want to output all the combinations of their values, to produce a list of lists in the order.

{
{a,e,f}
{a,e,g}
{a,e,h}
{a,e,i}
{a,e,j}
{b,e,f}
{b,e,g}
....
{d,e,j}
}
fjardon
  • 7,921
  • 22
  • 31
user5471528
  • 217
  • 2
  • 9
  • 1
    the word you want to Google is *permutations* – Ňɏssa Pøngjǣrdenlarp Oct 21 '15 at 12:45
  • 1
    @Plutonix Where do you see a [permutation](https://en.wikipedia.org/wiki/Permutation) ? The order is already set and not all the elements are involved... – fjardon Oct 21 '15 at 13:09
  • This is a nested loop or recursive solution – Osa E Oct 21 '15 at 13:14
  • There seems to be a good example here: http://stackoverflow.com/questions/27328235/dynamically-cross-join-multiple-different-size-collections-together-in-linq-c albeit c# or here: http://stackoverflow.com/questions/545703/combination-of-listlistint – Ric Oct 21 '15 at 13:18
  • 1
    @fjardon It seems that you haven't properly understood the definition in the link which you are posting. "Arranging all the members of a set into some sequence or order" includes also the starting order (and any variation of it). Thus and always according to the definition which you are posting, any set of elements where the order is relevant can be called permutation, as opposed to combination where the order doesn't matter. Here (as in any collection-based programming situation) the order does matter and thus permutations is the right name... – varocarbas Oct 21 '15 at 13:25
  • @fjardon... on the other hand if your comment was meant because of the "all the members" part (as per your definition), it might also be applied here by assuming that all the elements which are not being considered in a given case simply remain constant. Although all this would be bringing things a bit to the limit, as far as the conventional understanding of most of people (at least mine) regarding permutations is exactly what is being asked here, independently upon the fact of affecting all the elements or not. Thus googling "permutations" seems recommendable :) – varocarbas Oct 21 '15 at 13:33

4 Answers4

4

Introduction

What you want to do is called: cartesian product

Let's do some naming before going further. I will name your input lists L_i where 1 <= i <= n. I will also name S_i the size of the input list L_i.

We could ask the question: what is the size of the output ?

If there is only one list L_1, there will be S_1 output lists, each one containing exactly one element of L_1.

If there are two lists {L_1, L_2}. For each element of L_1 I can append S_2 different elements of L_2. As there are S_1 elements of L_1 it makes S_1*S_2 different output lists.

We can continue the reasoning to n lists and prove that the number of output lists will be: S_1*...*S_n.

How does that help us ? Because we can now create a mapping between a number i and an output list.

Given i a number 0<=i<S_1*...*S_n, the output list contains:

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

Implementation example

I don't know VB.net so I chose C# which uses the same .net platform. I decided to use a yield return function so that we don't allocate more memory than needed. If you just need to print the outputs it will only consume a single ulong of memory instead of allocating a very big list of output lists.

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("}");
        }
    }
}

The output is:

{
{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}
}

Limitation

One may ask : what if the product of the lists length overflows the variable used to index the outputs ?.

This is a real theoretical problem, but I use a ulong in my code and if the total number of ouput lists overflows this variable there is little chance that you can enumerate the output whatever method you use. (because the theoretical output will contain more than 2^64 lists).

Applications

The OP didn't explain why he needed this algorithm in the first place. So the reader may wonder why is this useful ?. One reason among others may be to generate test cases for regression testing. Let's say you have a legacy function taking as input three variables. You could generate some possible values for each of the parameters and using the cartesian product collect result of the function for each possible set of parameters. After refactoring the legacy code, you could ensure there is no regression by comparing the new code output and the legacy code output.

fjardon
  • 7,921
  • 22
  • 31
3

A simple way to implement with 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
throwit
  • 714
  • 3
  • 13
  • By understading this question as requesting a generic algorithm (not related to VB.NET at all), you shouldn't reply with a language-specific method (unless being a VB.NET one, logically). On the other hand and without being a Python expert, it seems (https://docs.python.org/2/library/itertools.html) that you cannot use `intertools.product` in this way. In fact, there is a SO question asking about all possible permutations of just 1 list (something much less complex), http://stackoverflow.com/questions/104420/how-to-generate-all-permutations-of-a-list-in-python and no answer delivers this. – varocarbas Oct 21 '15 at 13:53
  • I don't know Python and whether this example would work, but if it does, then this is an impressive capability. – JerryM Oct 21 '15 at 17:02
  • Up 1. This is indeed the correct solution in python and I would go for it. But the downside is it doesn't explain *how* to do it in another language :). (and yes python is awesome) – fjardon Oct 22 '15 at 08:12
3

This is a combination problem, not one of permutations. We want all combinations of 3 elements, one taken from each set. The order is driven by the sets, not the elements. The total number of combinations is the product of the counts of the set. In the example, that would be 4 x 1 x 5 = 20. Since we don't know how many lists there are (call it N). It we knew what N was ahead of time, this would be easy. We could write some nested loops to generate the combinations. Not knowing it is what's makes this tricky. Recursion is probably the most elegant way to solve it.

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

This function can be called as follows. This example assumes that the lists are members of another list called lists.

    Dim combinations As List(Of List(Of String))
    combinations = AppendCombinations(combinations, lists)
JerryM
  • 910
  • 6
  • 9
1

Here's a fairly simple-minded way of doing in (i.e. no Linq).

Assuming a form with a Button and a ListBox.

Storing everything in Lists for simplicity:

Private listOfLists As New List(Of List(Of String))
'Something to keep track of where we are...
Private permCount As New List(Of Integer)

Second list is just to keep track of progress through the permutations.

Load up the data...

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

And the rest...

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
Fruitbat
  • 764
  • 2
  • 5
  • 19