views:

675

answers:

5

Given this input: [1,2,3,4]

I'd like to generate the set of spanning sets:

[1] [2] [3] [4]
[1] [2] [3,4]
[1] [2,3] [4]
[1] [3] [2,4]
[1,2] [3] [4]
[1,3] [2] [4]
[1,4] [2] [3]
[1,2] [3,4]
[1,3] [2,4]
[1,4] [2,3]
[1,2,3] [4]
[1,2,4] [3]
[1,3,4] [2]
[2,3,4] [1]
[1,2,3,4]

Every set has all the elements of the original set, permuted to appear in unique subsets. What is the algorithm that produces these sets? I've tried Python generator functions using choose, permutation, combination, power set, and so on, but can't get the right combination.

20 Jan 2009

This is not a homework question. This is an improved answer I was working on for www.projecteuler.net problem # 118. I already had a slow solution but came up with a better way -- except I could not figure out how to do the spanning set.

I'll post my code when I get back from an Inauguration Party.

21 Jan 2009

This is the eventual algorithm I used:

def spanningsets(items):
    if len(items) == 1:
     yield [items]
    else:
     left_set, last = items[:-1], [items[-1]]
     for cc in spanningsets(left_set):
      yield cc + [last]
      for i,elem in enumerate(cc):
       yield cc[:i] + [elem + last] + cc[i+1:]

@Yuval F: I know how to do a powerset. Here's a straightforward implementation:

def powerset(s) :
    length = len(s)
    for i in xrange(0, 2**length) :
     yield [c for j, c in enumerate(s) if (1 << j) & i]
    return
+4  A: 

What about this? I haven't tested it yet, but I'll try it later…

I think this technique is called Dynamic Programming:

  1. Take the first element [1]
    What can you create with it? Only [1]

  2. Take the second one [2]
    Now you've got two possibilities: [1,2] and [1] [2]

  3. Take the third one [3]
    With the first of number 2 [1,2] one can create [1,2,3] and [1,2] [3]
    With the second of number 2 [1] [2] one can create [1,3] [2] and [1] [2,3] and [1] [2] [3]

I hope it is clear enough what I tried to show. (If not, drop a comment!)

Georg
+1 for the clear example. The general rule at each step is "put the element in one of the existing subsets, or in a new subset".
Zach Scrivena
It's clear, but just for the record it's not dynamic programming. DP is when you take a slow (e.g. exponential-time) recursive algorithm and record ("memoise") partial solutions so that they can be reused, leading to a faster (e.g. O(n^2)) algorithm.
j_random_hacker
Isn't dynamic programming if you create the solution for the smallest subset of the problem and with that, the solution the second smallest subset and so on?
Georg
gs: Whoops, I spoke too soon. Yes it is DP if you reuse the subsets generated by solving the smaller subproblems, instead of regenerating them.
j_random_hacker
I'm happy to know I haven't completely misunderstood DP :)
Georg
+9  A: 

This should work, though I haven't tested it enough.

def spanningsets(items):
    if not items: return
    if len(items) == 1:
        yield [[items[-1]]]
    else:
        for cc in spanningsets(items[:-1]):
            yield cc + [[items[-1]]]
            for i in range(len(cc)):
                yield cc[:i] + [cc[i] + [items[-1]]] + cc[i+1:]

for sset in spanningsets([1, 2, 3, 4]):
    print ' '.join(map(str, sset))

Output:

[1] [2] [3] [4]
[1, 4] [2] [3]
[1] [2, 4] [3]
[1] [2] [3, 4]
[1, 3] [2] [4]
[1, 3, 4] [2]
[1, 3] [2, 4]
[1] [2, 3] [4]
[1, 4] [2, 3]
[1] [2, 3, 4]
[1, 2] [3] [4]
[1, 2, 4] [3]
[1, 2] [3, 4]
[1, 2, 3] [4]
[1, 2, 3, 4]
dasony
I've added output.
J.F. Sebastian
Line 4 could be: yield [items]
recursive
This is a really odd generator function. I've never seen one that had the recursion in the outer loop nor one that had two yields in the else-stmt. I'm definitely going to have to think this through.Thanks for a great solution!
hughdbrown
@recursive: It initially was so. I've changed it to `yield [[items[-1]]]` to stress similarity with the yields inside the loops.
J.F. Sebastian
A: 

Here's The SUNY algorithm repository page on the problem. Maybe you can translate one of the code references to python.

Edit: This was a similar problem. Here is the SUNY repository page about generating partitions, which I believe is the correct problem.

Yuval F
+1: References to algorithms known to be correct and complete.
S.Lott
It is *related* but it is not the *same* problem. A power set is not an answer to the problem.
J.F. Sebastian
Agreed. I edited my answer accordingly.
Yuval F
A: 

The result sets together with the empty set {} looks like the results of the powerset (or power set), but it is not the same thing.

I started a post about a similar problem which has a few implementations (although in C#) and geared more for speed than clarity in some cases. The first example should be easy to translate. Maybe it will give a few ideas anyway.

They work on the principle that emmumerating the combinations is similar to counting in binary (imagine counting from 0 to 16). You do not state if the order is important, or just generating all the combinations, so a quick tidy up may be in order afterwards.

Have a look here (ignore the odd title, the discussion took another direction)

jheppinstall
-1: Nope, the powerset includes non-spanning subsets.
Mr Fooz
Edited. Just out of interest, what is the difference between the Spanning set and the powerset? The results for the example would be the same wouldnt they? I know that this doesnt mean ut would hold for all examples.
jheppinstall
The powerset is the set of all subsets. Each member of the spanning set is a set of sets whose union is the original set.
recursive
E.g. the powerset of {1, 2, 3} is {{}, {1}, {2}, {1, 2}, {3}, {1, 3}, {2, 3}, {1, 2, 3}}. There are always 2^n sets in the powerset because each of the n base elements can be either present or absent in a set, just like each bit in an n-bit word can be on or off.
j_random_hacker
A: 

I think the following method is the best way to generate them for the euler problem, as you can replace the return value with the number of prime spanning subsets, and it will be trivial to do the multiplication (especially with memoization):

GenerateSubsets(list)
    partitions = { x | x is subset of list and x contains the lowest element of list }
    foreach (parition in partitions)
        if set == list
            yield { partition }
        else
            yield { partition } x GenerateSubsets(list - part)

The key part is to make sure that the recursive side always has the leftmost element, this way, you don't get duplicates.

I have some messy C# code that does this:

    IEnumerable<IEnumerable<List<int>>> GenerateSubsets(List<int> list)
    {
        int p = (1 << (list.Count)) - 2;
        List<int> lhs = new List<int>();
        List<int> rhs = new List<int>();
        while (p >= 0)
        {
            for (int i = 0; i < list.Count; i++)
                if ((p & (1 << i)) == 0)
                    lhs.Add(list[i]);
                else
                    rhs.Add(list[i]);

            if (rhs.Count > 0)
                foreach (var rhsSubset in GenerateSubsets(rhs))
                    yield return Combine(lhs, rhsSubset);
            else
                yield return Combine(lhs, null);

            lhs.Clear();
            rhs.Clear();
            p -= 2;
        }
    }

    IEnumerable<List<int>> Combine(List<int> list, IEnumerable<List<int>> rest)
    {
        yield return list;
        if (rest != null)
            foreach (List<int> x in rest)
                yield return x;
    }
FryGuy