6

Suppose I have two lists, how do I iterate through every possible combination of every sublist, such that each item appears once and only once.

I guess an example could be if you have employees and jobs and you want split them into teams, where each employee can only be in one team and each job can only be in one team. Eg

List<string> employees = new List<string>() { "Adam", "Bob"}  ;
List<string> jobs      = new List<string>() { "1", "2", "3"};

I want

Adam       : 1
Bob        : 2 , 3

Adam       : 1 , 2
Bob        : 3

Adam       : 1 , 3
Bob        : 2

Adam       : 2 
Bob        : 1 , 3

Adam       : 2 , 3
Bob        : 1

Adam       : 3
Bob        : 1 , 2

Adam, Bob  : 1, 2, 3

I tried using the answer to this stackoverflow question to generate a list of every possible combination of employees and every possible combination of jobs and then select one item from each from each list, but that's about as far as I got.

I don't know the maximum size of the lists, but it would be certainly be less than 100 and there may be other limiting factors (such as each team can have no more than 5 employees)

Update

Not sure whether this can be tidied up more and/or simplified, but this is what I have ended up with so far.

It uses the Group algorithm supplied by Yorye (see his answer below), but I removed the orderby which I don't need and caused problems if the keys are not comparable.

var employees = new List<string>() { "Adam", "Bob"  } ;
var jobs      = new List<string>() { "1", "2", "3"  };

int c= 0;
foreach (int noOfTeams in Enumerable.Range(1, employees.Count))
{   
    var hs = new HashSet<string>();

    foreach( var grouping in Group(Enumerable.Range(1, noOfTeams).ToList(), employees))
    {   
        // Generate a unique key for each group to detect duplicates.   
        var key = string.Join(":" , 
                      grouping.Select(sub => string.Join(",", sub)));           

        if (!hs.Add(key))
            continue;

        List<List<string>> teams = (from r in grouping select r.ToList()).ToList();

        foreach (var group in Group(teams, jobs))
        {
            foreach (var sub in group)
            {               
                Console.WriteLine(String.Join(", " , sub.Key )   + " : " + string.Join(", ", sub));
            }
            Console.WriteLine();
            c++;
        }
    }

}           
Console.WriteLine(String.Format("{0:n0} combinations for {1} employees and {2} jobs" , c , employees.Count, jobs.Count));  

Since I'm not worried about the order of the results, this seems to give me what I need.

Community
  • 1
  • 1
sgmoore
  • 15,694
  • 5
  • 43
  • 67

5 Answers5

5

Good question.

First of all before you write your code down, lets understand the underlying combinatorics of your question.

Basically, you require that for any partition of set A, you require the same number of parts in set B.

E.g. If you split set A to 3 groups you require that set B will be split to 3 groups as well, if you don't at least one element won't have a corresponding group in the other set.
It's easier to picture it with splitting set A to 1 group we must have one group made from set B as in your example (Adam, Bob : 1, 2, 3) .

So now, we know that set A has n elements and that set B has k elements.
So naturally we cannot request that any set be split more that Min(n,k).

Let's say we split both sets to 2 groups (partitions) each, now we have 1*2=2! unique pairs between the two sets.
Another example is 3 groups each would give us 1*2*3=3! unique pairs.

However, we're still not finished, after any set is split into several subsets (groups), we can still order the elements in many combinations.
So for m partitions of a set we need to find how many combinations of placing n elements in m partitions.
This can be found by using the Stirling number of the second kind formula:

(Eq 1) Stirling Number of the Second Kind

This formula gives you The number of ways of partitioning a set of n elements into k nonempty sets.

So the total number of combinations of pairs for all partitions from 1 to min(n,k) is:

(Eq 2) All combinations

In short, it's the sum of all partition combinations of both sets, times all combinations of pairs.

So now we understand how to partition and pair our data we can write the code down:

Code:

So basically, if we look at our final equation (2) we understand the we need four parts of code to our solution.
1. A summation (or loop)
2. A way to get our Stirling sets or partitions from both sets
3. A way to get the Cartesian product of both Stirling sets.
4. A way to permutate through the items of a set. (n!)

On StackOverflow you can find many ways of how to permuatate items and how to find cartesian products, here is a an example (as extension method):

 public static IEnumerable<IEnumerable<T>> Permute<T>(this IEnumerable<T> list)
    {
        if (list.Count() == 1)
            return new List<IEnumerable<T>> { list };

        return list.Select((a, i1) => Permute(list.Where((b, i2) => i2 != i1)).Select(b => (new List<T> { a }).Union(b)))
                   .SelectMany(c => c);
    }

This was the easy part of the code.
The more difficult part (imho) is finding all possible n partitions of a set.
So to solve this, I first solved the greater problem of how to find all possible partitions of a set (not just of size n).

I came up with this recursive function:

public static List<List<List<T>>> AllPartitions<T>(this IEnumerable<T> set)
    {
        var retList = new List<List<List<T>>>();

        if (set == null || !set.Any())
        {
            retList.Add(new List<List<T>>());
            return retList;
        }
        else
        {
            for (int i = 0; i < Math.Pow(2, set.Count()) / 2; i++)
            {
                var j = i;

                var parts = new [] { new List<T>(), new List<T>() };


                foreach (var item in set)
                {
                    parts[j & 1].Add(item);
                    j >>= 1;
                }

                foreach (var b in AllPartitions(parts[1]))
                {
                    var x = new List<List<T>>();

                    x.Add(parts[0]);

                    if (b.Any())
                        x.AddRange(b);

                    retList.Add(x);
                }
            }
        }
        return retList;
    }

The return value of : List<List<List<T>>> just means a List of all possible partitions where a partition is a list of sets and a set is a list of elements.
We do not have to use the type List here, but it simplifies indexing.

So now let's put everything together:

Main Code

//Initialize our sets
        var employees = new [] { "Adam", "Bob" };
        var jobs = new[] {  "1", "2", "3" };

        //iterate to max number of partitions (Sum)
        for (int i = 1; i <= Math.Min(employees.Length, jobs.Length); i++)
        {
            Debug.WriteLine("Partition to " + i + " parts:");

            //Get all possible partitions of set "employees" (Stirling Set)
            var aparts = employees.AllPartitions().Where(y => y.Count == i);
            //Get all possible partitions of set "jobs" (Stirling Set)
            var bparts = jobs.AllPartitions().Where(y => y.Count == i);

            //Get cartesian product of all partitions 
            var partsProduct = from employeesPartition in aparts
                      from jobsPartition in bparts
                               select new {  employeesPartition,  jobsPartition };

            var idx = 0;
            //for every product of partitions
            foreach (var productItem in partsProduct)
            {
                //loop through the permutations of jobPartition (N!)
                foreach (var permutationOfJobs in productItem.jobsPartition.Permute())
                {
                    Debug.WriteLine("Combination: "+ ++idx);

                    for (int j = 0; j < i; j++)
                    {
                        Debug.WriteLine(productItem.employeesPartition[j].ToArrayString() + " : " + permutationOfJobs.ToArray()[j].ToArrayString());
                    }
                }
            }
        }

Output:

Partition to 1 parts:
Combination: 1
{ Adam , Bob } : { 1 , 2 , 3 }
Partition to 2 parts:
Combination: 1
{ Bob } : { 2 , 3 }
{ Adam } : { 1 }
Combination: 2
{ Bob } : { 1 }
{ Adam } : { 2 , 3 }
Combination: 3
{ Bob } : { 1 , 3 }
{ Adam } : { 2 }
Combination: 4
{ Bob } : { 2 }
{ Adam } : { 1 , 3 }
Combination: 5
{ Bob } : { 3 }
{ Adam } : { 1 , 2 }
Combination: 6
{ Bob } : { 1 , 2 }
{ Adam } : { 3 }

We can easily check our out put just by counting the results.
In this example we have a set of 2 elements, and a set of 3 elements, Equation 2 states that we need S(2,1)S(3,1)1!+S(2,2)S(3,2)2! = 1+6 = 7
which is exactly the number of combinations we got.

For reference here are examples of Stirling number of the second kind:
S(1,1) = 1

S(2,1) = 1
S(2,2) = 1

S(3,1) = 1
S(3,2) = 3
S(3,3) = 1

S(4,1) = 1
S(4,2) = 7
S(4,3) = 6
S(4,4) = 1

Edit 19.6.2012

public static String ToArrayString<T>(this IEnumerable<T> arr)
    {
        string str = "{ ";

        foreach (var item in arr)
        {
            str += item + " , ";
        }

        str = str.Trim().TrimEnd(',');
        str += "}";

        return str;
    }

Edit 24.6.2012

The main part of this algorithm is finding the Stirling sets, I've used an inneficient Permutation method, here is a faster one based on the QuickPerm algorithm:

public static IEnumerable<IEnumerable<T>> QuickPerm<T>(this IEnumerable<T> set)
    {
        int N = set.Count();
        int[] a = new int[N];
        int[] p = new int[N];

        var yieldRet = new T[N];

        var list = set.ToList();

        int i, j, tmp ;// Upper Index i; Lower Index j
        T tmp2;

        for (i = 0; i < N; i++)
        {
            // initialize arrays; a[N] can be any type
            a[i] = i + 1; // a[i] value is not revealed and can be arbitrary
            p[i] = 0; // p[i] == i controls iteration and index boundaries for i
        }
        yield return list;
        //display(a, 0, 0);   // remove comment to display array a[]
        i = 1; // setup first swap points to be 1 and 0 respectively (i & j)
        while (i < N)
        {
            if (p[i] < i)
            {
                j = i%2*p[i]; // IF i is odd then j = p[i] otherwise j = 0

                tmp2 = list[a[j]-1];
                list[a[j]-1] = list[a[i]-1];
                list[a[i]-1] = tmp2;

                tmp = a[j]; // swap(a[j], a[i])
                a[j] = a[i];
                a[i] = tmp;

                //MAIN!

               // for (int x = 0; x < N; x++)
                //{
                //    yieldRet[x] = list[a[x]-1];
                //}
                yield return list;
                //display(a, j, i); // remove comment to display target array a[]

                // MAIN!

                p[i]++; // increase index "weight" for i by one
                i = 1; // reset index i to 1 (assumed)
            }
            else
            {
                // otherwise p[i] == i
                p[i] = 0; // reset p[i] to zero
                i++; // set new index value for i (increase by one)
            } // if (p[i] < i)
        } // while(i < N)
    }

This will take cut the time in half.
However, most of the CPU cycles go to string building, which is specifically needed for this example.
This will be a make it a bit faster:

             results.Add(productItem.employeesPartition[j].Aggregate((a, b) => a + "," + b) + " : " + permutationOfJobs.ToArray()[j].Aggregate((a, b) => a + "," + b));

Compiling in x64 will be better cause these strings are taking a lot of memory.

Erez Robinson
  • 794
  • 4
  • 9
  • Thanks. It will take me a while to go through and understand this. I downloaded the code and it produces the same number of results as my current algorithm (based on Yorye's answer). However it is quite a bit slower (almost ten times) and time is a very significant factor. – sgmoore Jun 14 '12 at 20:35
  • Sorry, But Yorye's example doesn't work for me, some IComparable exception, so I can't compare their their complexity. – Erez Robinson Jun 17 '12 at 11:07
  • Just rem out the line .OrderBy(x => x.Key) – sgmoore Jun 18 '12 at 17:47
  • I've done some checking, my solution is faster, actually for 6 employees and 3 jobs my solution took less than 15ms without console output, and Yorye's solution took 83ms. Please check the algorithms without Console output, because there is a big difference in Debug.WriteLine and Console.Writeline, and output streams tend to misjudge algorithm complexity. I hope you will be satisfied. – Erez Robinson Jun 19 '12 at 09:44
  • Furthermore, I didn't add my implemetation of "ToArrayString" so it could be that you implemented it differently. – Erez Robinson Jun 19 '12 at 09:48
  • If you want you can look at my test code at http://pastebin.com/bF8yNPbA - It would appear from my tests that your result is faster for smaller data sets, but slower for larger sets. – sgmoore Jun 23 '12 at 21:15
  • Yes, You are correct, however please look at my edit. Use QuickPerm instead of Permute, and change the string concatenation to Aggregate. Please understand that my algorithm works for all T, the other algo works for T=String, and needs some work to work with other T. If you don't need this string output my algo is pretty fast, just comment out the string concatenation and see. – Erez Robinson Jun 24 '12 at 09:14
  • I've optimized the QuickPerm method, the algorithm is now 3 times faster! :) – Erez Robinson Jun 28 '12 at 20:42
3

Would you be ok using another library? Here is a generic library for combinations (you obviously want the one with no repeat). Then you'd only need to do a foreach over your employees list and run the combination w/ both.

I don't think you're doing yourself any favors from a big O perspective, is efficiency a priority here?

This is from the hip, but this should be the code to get what you want (with that library):

Combinations<string> combinations = new Combinations<string>(jobs, 2);

foreach(IList<string> c in combinations) {
  Console.WriteLine(String.Format("{{{0} {1}}}", c[0], c[1]));
}

And then that would need to be applied to each employee

Rikon
  • 2,688
  • 3
  • 22
  • 32
  • `new Combinations(jobs, 2);` Will generate combinations of `jobs` each being of size 2 or more, it will NOT generate all combinations of combinations, with sub-combinations count equals 2. You will only get `{1,2} {1,3} {2,3}` - now what? – SimpleVar Jun 04 '12 at 20:58
1

In my answer I will ignore the last result you had: Adam, Bob: 1, 2, 3, because it is an exception logically. Skip to the end to see my output.

Explanation:

The idea would be iterating combinations of "which group an element will belong to".

Say you have elements "a, b, c", and you have groups "1, 2", lets have an array of size 3, as the number of elements, which will contain all possible combinations of the groups "1, 2", WITH repetition:

{1, 1, 1} {1, 1, 2} {1, 2, 1} {1, 2, 2}
{2, 1, 1} {2, 1, 2} {2, 2, 1} {2, 2, 2}

Now we shall take each group, and sort of make a key-value collection out of it, with the following logic:

Group of elements[i] will be the value of comb[i].

Example with {1, 2, 1}:
a: group 1
b: group 2
c: group 1

And in a different view, the way you wanted it:
group 1: a, c
group 2: b

After all that, you just have to filter all the combinations that do not contain all groups, because you wanted all groups to have at least one value.

So you should check if all groups appear in a certain combination and filter the ones that don't match, so you'll be left with:

{1, 1, 2} {1, 2, 1} {1, 2, 2}
{2, 1, 2} {2, 2, 1} {2, 1, 1}

Which will result in:

1: a, b
2: c

1: a, c
2: b

1: a
2: b, c

1: b
2: a, c

1: c
2: a, b

1: b, c
2: a

This group-breaking combination logic will work for bigger amount of elements and groups as well. Here is my implementation, which can probably be done better, because even I got a little lost in there while I coded it (it's really not an intuitive problem hehe), but it works good.

Implementation:

public static IEnumerable<ILookup<TValue, TKey>> Group<TKey, TValue>
    (List<TValue> keys,
     List<TKey> values,
     bool allowEmptyGroups = false)
{
    var indices = new int[values.Count];
    var maxIndex = values.Count - 1;
    var nextIndex = maxIndex;
    indices[maxIndex] = -1;

    while (nextIndex >= 0)
    {
        indices[nextIndex]++;

        if (indices[nextIndex] == keys.Count)
        {
            indices[nextIndex] = 0;
            nextIndex--;
            continue;
        }

        nextIndex = maxIndex;

        if (!allowEmptyGroups && indices.Distinct().Count() != keys.Count)
        {
            continue;
        }

        yield return indices.Select((keyIndex, valueIndex) =>
                                    new
                                        {
                                            Key = keys[keyIndex],
                                            Value = values[valueIndex]
                                        })
            .OrderBy(x => x.Key)
            .ToLookup(x => x.Key, x => x.Value);
    }
}

Usage:

var employees = new List<string>() { "Adam", "Bob"};
var jobs      = new List<string>() { "1", "2", "3"};
var c = 0;

foreach (var group in CombinationsEx.Group(employees, jobs))
{
    foreach (var sub in group)
    {
        Console.WriteLine(sub.Key + ": " + string.Join(", ", sub));
    }

    c++;
    Console.WriteLine();
}

Console.WriteLine(c + " combinations.");

Output:

Adam: 1, 2
Bob: 3

Adam: 1, 3
Bob: 2

Adam: 1
Bob: 2, 3

Adam: 2, 3
Bob: 1

Adam: 2
Bob: 1, 3

Adam: 3
Bob: 1, 2

6 combinations.

UPDATE

Combined keys combinations prototype:

public static IEnumerable<ILookup<TKey[], TValue>> GroupCombined<TKey, TValue>
    (List<TKey> keys,
     List<TValue> values)
{
    // foreach (int i in Enumerable.Range(1, keys.Count))
    for (var i = 1; i <= keys.Count; i++)
    {
        foreach (var lookup in Group(Enumerable.Range(0, i).ToList(), keys))
        {
            foreach (var lookup1 in
                     Group(lookup.Select(keysComb => keysComb.ToArray()).ToList(),
                           values))
            {
                yield return lookup1;
            }
        }
    }

    /*
    Same functionality:

    return from i in Enumerable.Range(1, keys.Count)
           from lookup in Group(Enumerable.Range(0, i).ToList(), keys)
           from lookup1 in Group(lookup.Select(keysComb =>
                                     keysComb.ToArray()).ToList(),
                                 values)
           select lookup1;
    */
}

There's still a little problem of duplicates, but it produces all results.

Here is what I would use to remove duplicates, as a temporary solution:

var c = 0;
var d = 0;

var employees = new List<string> { "Adam", "Bob", "James" };
var jobs = new List<string> {"1", "2"};

var prevStrs = new List<string>();

foreach (var group in CombinationsEx.GroupCombined(employees, jobs))
{
    var currStr = string.Join(Environment.NewLine,
                              group.Select(sub =>
                                           string.Format("{0}: {1}",
                                               string.Join(", ", sub.Key),
                                               string.Join(", ", sub))));

    if (prevStrs.Contains(currStr))
    {
        d++;
        continue;
    }

    prevStrs.Add(currStr);

    Console.WriteLine(currStr);
    Console.WriteLine();
    c++;
}

Console.WriteLine(c + " combinations.");
Console.WriteLine(d + " duplicates.");

Output:

Adam, Bob, James: 1, 2

Adam, Bob: 1
James: 2

James: 1
Adam, Bob: 2

Adam, James: 1
Bob: 2

Bob: 1
Adam, James: 2

Adam: 1
Bob, James: 2

Bob, James: 1
Adam: 2

7 combinations.
6 duplicates.

Note that it will also produce non-combined groups (if possible - because no empty groups are allowed). To produce ONLY combined-keys you will need to replace this:

for (var i = 1; i <= keys.Count; i++)

With this:

for (var i = 1; i < keys.Count; i++)

In the beginning of the GroupCombined method. Test the method with three employees and three jobs to see how it works exactly.

ANOTHER EDIT:

A better duplicates-handling would be handling duplicate key-combinations in the GroupCombined level:

public static IEnumerable<ILookup<TKey[], TValue>> GroupCombined<TKey, TValue>
    (List<TKey> keys,
     List<TValue> values)
{
    for (var i = 1; i <= keys.Count; i++)
    {
        var prevs = new List<TKey[][]>();

        foreach (var lookup in Group(Enumerable.Range(0, i).ToList(), keys))
        {
            var found = false;
            var curr = lookup.Select(sub => sub.OrderBy(k => k).ToArray())
                .OrderBy(arr => arr.FirstOrDefault()).ToArray();

            foreach (var prev in prevs.Where(prev => prev.Length == curr.Length))
            {
                var isDuplicate = true;

                for (var x = 0; x < prev.Length; x++)
                {
                    var currSub = curr[x];
                    var prevSub = prev[x];

                    if (currSub.Length != prevSub.Length ||
                        !currSub.SequenceEqual(prevSub))
                    {
                        isDuplicate = false;
                        break;
                    }
                }

                if (isDuplicate)
                {
                    found = true;
                    break;
                }
            }

            if (found)
            {
                continue;
            }

            prevs.Add(curr);

            foreach (var group in
                     Group(lookup.Select(keysComb => keysComb.ToArray()).ToList(),
                           values))
            {
                yield return group;
            }
        }
    }
}

As it seems, it would be smart to add constraints to the method so that TKey will be ICompareable<TKey> and maybe also IEquatable<TKey>.

This results with 0 duplicates in the end.

SimpleVar
  • 14,044
  • 4
  • 38
  • 60
  • Fistly, I should say I do appreciate your efforts and I think this has got me closer to where I need to be. However, if you add Charlie to the list of employee, your algorithm does not give results such as Adam, Bob working on Jobs 1 and 2 with Charlie working on 3. I **think** this might be resolved by first of all producing a list of all possible team combinations and then allocating the jobs to each team using your group method, which is what I am trying to do now. – sgmoore Jun 07 '12 at 13:11
  • @sgmoore See update :) I made another method for generating combinations with combined keys (such as `Adam, Bob`), and a third method that wraps them both. Check it out and let me know how you like it. – SimpleVar Jun 07 '12 at 15:04
  • I'm trying to work out exactly what is this routine is doing, but if you try calling GroupCombined with three employees, it returns all sort of weird results. – sgmoore Jun 07 '12 at 15:59
  • @sgmoore You're right, I edited that part. The general idea to do what you want next, is using Group with dummy-list as keys and actual-keys as values. The dummy-list will simply let you index into the key-combination. Then you have all the combinations of keys, each combination group being the keys with the real values. It's kind of hard to explain (and follow). – SimpleVar Jun 08 '12 at 12:19
  • Just a update. I have edited my question to include a possible answer based on your group algorithm. Still have a few tests to run and if they pass then I can mark your answer as the accepted one. – sgmoore Jun 08 '12 at 20:27
  • @sgmoore I've reviewed your update, and was also thinking of simply checking for duplicates and ignore them, but that really isn't the perfect solution. There's just some combinotorics-stuff I haven't figured out there quiet yet. But of course, ignoring duplicates will work just fine. See my example for how I would ignore duplicates (more readable, I believe). – SimpleVar Jun 09 '12 at 10:00
  • Your version is **much** more readable (and I think I will steal another bit of your code). But because its removes the duplicates **after** all the combinations are calculated, it is much slower for larger sets. Eg a 5 x 7 matrix produces 4,306,681 combinations and then throws 96% of them away. (Also when you get to that number of items HashSet is much faster than List) – sgmoore Jun 09 '12 at 14:27
  • It only calculates the current combination group in each iteration, but yes, hashset would be faster. The point was mainly to "compare" with previous data in the form of the string which is needed anyway, instead of arrays and lists of combinations keys and values, etc. Other than that - the whole algorithm isn't too good for large numbers - because the duplicates will be there either way, which is something we would like to avoid. What we COULD do is handle the duplicates in a lower level. See my edit. – SimpleVar Jun 09 '12 at 14:57
  • I haven't figured out what, but there must be an error with your latest change as it does not produce the same number of results as your earlier revision which I am fairly sure was correct. If you try four employees and three jobs you get 55 results whereas I think you should get 79. The goods news is I have integrated your earlier answer (ie the one I have added to my question) into my code and tested in one some real data and it produces the results that I expect and in reasonable time. So if you want to perhaps change this answer back, I will mark it as my accepted answer. – sgmoore Jun 14 '12 at 20:08
  • @sgmoore Glad it worked out eventually! Are you referring to my 1st piece of code seen in my answer or the GroupCombined of the last version? – SimpleVar Jun 15 '12 at 13:43
  • Just the new version of GroupCombined routine. – sgmoore Jun 15 '12 at 19:55
  • @sgmoore The one currently in the answer? – SimpleVar Jun 16 '12 at 01:04
  • Yes, the one currently in your answer. – sgmoore Jun 16 '12 at 18:39
  • I'm not following. You said that an older version worked and the one currently in the answer doesn't. Could you [pastebin](http://pastebin.com/) the working version and differences in results from the one in the answer? – SimpleVar Jun 16 '12 at 19:16
  • See http://pastebin.com/S44UXnLT . This contains both your old and new versions of your GroupCombined methods and if you swap them around you will see the NewGroupCombined gives 55 results whereas OldGroupCombined gives 79 which is the correct. – sgmoore Jun 18 '12 at 17:40
  • @sgmoore I found a bug in the new version, and fixed it (edited). It was in the part where I look to see if the keys-grouping exists in `prevs`. I needed to add the `isDuplicate` variable to test for one-on-one equality, and the `found` variable indicates a general "one-exists-in-many". The new version now produces 79 results as well, and 0 duplicates. – SimpleVar Jun 18 '12 at 23:47
  • And may I add: Thank you for this nice challenge! I enjoy questions such as this one, that occupies me and develop me as well. – SimpleVar Jun 18 '12 at 23:51
0

Say we have two sets, A and B.
A = {a1, a2, a3} ; B = {b1, b2, b3}

First, let's get a set consisting of tuples containing a subset, and its complement:

{a1} {a2 a3} 
{a2} {a1 a3}
{a3} {a1 a2}
{a2, a3} {a1}
...

For this, you can use Rikon's library above, or write your own code. You'll need to do this:

  1. Get a set of all subsets (known as the power set)
  2. For each subset, get the complement, or leftover elements of that subset from the greater set.
  3. Join these in a tuple or pair; e.g. (subset, complement)

Order matters here; {a1} {a2 a3} and {a2 a3} {a1} are different tuples, after all.

Then we get a similar set for B. Then, we perform a cross join between the two sets, to get:

{a1} {a2 a3} | {b1} {b2 b3}

{a2} {a1 a3} | {b2} {b1 b3}
...

This pretty much matches your description above. Just look at {Bob, Adam} as one set, and {1, 2, 3} as another set. You'll have to dump some of the empty sets (since a power set includes empty subsets too), depending on your requirements. This is the general gist, though, as far as I can tell. Sorry for the lack of code; I gotta go to sleep :P

GregRos
  • 8,667
  • 3
  • 37
  • 63
  • I can understand how this works if the number of teams is just two. (It is only two in my example, because there are just two employees). In your example, not only do I have to consider {a1} {a2 a3} , but always {a1} {a2} {a3}. I'm guessing this will involve recursion, but that's where I seem to going around in circles? – sgmoore Jun 05 '12 at 10:43
0

Ignoring your other constraints ( such as maximum size of team ), what you are doing is creating a partition of set P and a partition of set Q with the same number of subsets, then finding all combinations of one of the sets to map the first partition to the second.

Michael Orlov's paper has what appears to be a simple algorithm for generating partitions where each iteration uses constant space in this paper. He also provides an algorithm for listing partitions of a given size.

Start with P = { A, B } and Q = { 1, 2, 3 } then the partitions of size 1 are [ P ] and [ Q ], so the only pairing is ( [ P ], [ Q ] )

For partitions of size 2 , P has only two elements so only one partition of size 2 [ { A }, { B } ], Q has three partitions of size 2 [ { 1 }, { 2, 3 } ], [ { 1, 2 }, { 3 } ], [ { 1, 3 }, { 2 } ].

As each partition of Q contains two subsets, there are 2 combinations of each partition, which gives you six pairings:

( [ { A }, { B } ], [ { 1 }, { 2, 3 } ] )
( [ { A }, { B } ], [ { 2, 3 }, { 1 } ] )
( [ { A }, { B } ], [ { 1, 2 }, { 3 } ] )
( [ { A }, { B } ], [ { 3 }, { 1, 2 } ] )
( [ { A }, { B } ], [ { 1, 3 }, { 2 } ] ) 
( [ { A }, { B } ], [ { 2 }, { 1, 3 } ] ) 

As the size of one of the original sets was 2, there are no partitions of size 3 of it, so we stop.

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171