11

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
hughdbrown
  • 47,733
  • 20
  • 85
  • 108

5 Answers5

11

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]
jfs
  • 399,953
  • 195
  • 994
  • 1,670
dasony
  • 3,708
  • 2
  • 22
  • 20
  • 1
    Line 4 could be: yield [items] – recursive Jan 20 '09 at 18:17
  • 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 Jan 20 '09 at 22:32
  • @recursive: It initially was so. I've changed it to `yield [[items[-1]]]` to stress similarity with the yields inside the loops. – jfs Jan 21 '09 at 06:30
  • By the way, I think it's better to pass just the position in the initial list instead of passing the whole part of the list `spanningsets(items[:-1])`. This would gain us saving some memory. And would require to make some helper function, for example [this way](http://ideone.com/vEAjH) – ovgolovin Feb 08 '12 at 20:40
6

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 Schölly
  • 124,188
  • 49
  • 220
  • 267
  • 1
    +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 Jan 20 '09 at 10:32
  • 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 Jan 21 '09 at 13:20
  • 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 Schölly Jan 21 '09 at 15:34
  • 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 Jan 22 '09 at 09:11
  • I'm happy to know I haven't completely misunderstood DP :) – Georg Schölly Jan 22 '09 at 10:33
0

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
  • 20,565
  • 5
  • 44
  • 69
0

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
  • 8,614
  • 3
  • 33
  • 47
-1

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)

Community
  • 1
  • 1
jheppinstall
  • 2,338
  • 4
  • 23
  • 27
  • 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 Jan 20 '09 at 17:04
  • 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 Jan 20 '09 at 18:05
  • 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 Jan 21 '09 at 13:23