81

I am writing a program in Python, and I realized that a problem I need to solve requires me, given a set S with n elements (|S|=n), to test a function on all possible subsets of a certain order m (i.e. with m number of elements). To use the answer to produce a partial solution, and then try again with the next order m=m+1, until m=n.

I am on my way to write a solution of the form:

def findsubsets(S, m):
    subsets = set([])
    ...
    return subsets

But knowing Python I expected a solution to be already there.

What is the best way to accomplish this?

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
Pietro Speroni
  • 3,131
  • 11
  • 44
  • 55
  • `scipy.misc.comb(S, m)` gives the number of subsets you will get. You should eventually make a check before you execute your code as the number of m-sized subsets of S gets very big very fast. – Martin Thoma Feb 20 '15 at 17:19
  • Literally had the same problem, set out to code it myself and then realized that there must exist a Python library for this! – Srini Nov 10 '16 at 18:23

10 Answers10

135

itertools.combinations is your friend if you have Python 2.6 or greater. Otherwise, check the link for an implementation of an equivalent function.

import itertools
def findsubsets(S,m):
    return set(itertools.combinations(S, m))

S: The set for which you want to find subsets
m: The number of elements in the subset

recursive
  • 83,943
  • 34
  • 151
  • 241
  • 6
    i would not return a set, but simply return the iterator (or just use combinations() without defining a findsubsets()...) –  Dec 17 '08 at 14:23
  • @hop the OP particularly asked for sets. Omitting the set cast allows repetitions in different orders, eg: (1,2,3), (2,3,1), (3,1,2)... – James Bradbury Feb 10 '16 at 14:30
  • @JamesBradbury: I'm sorry, I don't understand what you mean. Are you confusing this with permutations? –  Feb 10 '16 at 14:58
64

Using the canonical function to get the powerset from the the itertools recipe page:

from itertools import chain, combinations

def powerset(iterable):
    """
    powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)
    """
    xs = list(iterable)
    # note we return an iterator rather than a list
    return chain.from_iterable(combinations(xs,n) for n in range(len(xs)+1))

Used like:

>>> list(powerset("abc"))
[(), ('a',), ('b',), ('c',), ('a', 'b'), ('a', 'c'), ('b', 'c'), ('a', 'b', 'c')]

>>> list(powerset(set([1,2,3])))
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]

map to sets if you want so you can use union, intersection, etc...:

>>> map(set, powerset(set([1,2,3])))
[set([]), set([1]), set([2]), set([3]), set([1, 2]), set([1, 3]), set([2, 3]), set([1, 2, 3])]

>>> reduce(lambda x,y: x.union(y), map(set, powerset(set([1,2,3]))))
set([1, 2, 3])
Taylor D. Edmiston
  • 12,088
  • 6
  • 56
  • 76
brice
  • 24,329
  • 7
  • 79
  • 95
26

Here's a function that gives you all subsets of the integers [0..n], not just the subsets of a given length:

from itertools import combinations, chain

def allsubsets(n):
    return list(chain(*[combinations(range(n), ni) for ni in range(n+1)]))

so e.g.

>>> allsubsets(3)
[(), (0,), (1,), (2,), (0, 1), (0, 2), (1, 2), (0, 1, 2)]
Ekrem Dinçel
  • 1,053
  • 6
  • 17
alex
  • 195
  • 2
  • 6
5

Here is one neat way with easy to understand algorithm.

import copy

nums = [2,3,4,5]
subsets = [[]]

for n in nums:
    prev = copy.deepcopy(subsets)
    [k.append(n) for k in subsets]
    subsets.extend(prev)

print(subsets) 
print(len(subsets))

# [[2, 3, 4, 5], [3, 4, 5], [2, 4, 5], [4, 5], [2, 3, 5], [3, 5], [2, 5], [5], 
# [2, 3, 4], [3, 4], [2, 4], [4], [2, 3], [3], [2], []]

# 16 (2^len(nums))

darxtrix
  • 2,032
  • 2
  • 23
  • 30
4

Here's some pseudocode - you can cut same recursive calls by storing the values for each call as you go and before recursive call checking if the call value is already present.

The following algorithm will have all the subsets excluding the empty set.

list * subsets(string s, list * v) {

    if(s.length() == 1) {
        list.add(s);    
        return v;
    }
    else
    {
        list * temp = subsets(s[1 to length-1], v);
        int length = temp->size();

        for(int i=0;i<length;i++) {
            temp.add(s[0]+temp[i]);
        }

        list.add(s[0]);
        return temp;
    }
}

So, for example if s = "123" then output is:

1
2
3
12
13
23
123
eldarerathis
  • 35,455
  • 10
  • 90
  • 93
kofhearts
  • 3,607
  • 8
  • 46
  • 79
3

Without using itertools:

In Python 3 you can use yield from to add a subset generator method to buit-in set class:

class SetWithSubset(set):
    def subsets(self):
        s1 = []
        s2 = list(self)

        def recfunc(i=0):            
            if i == len(s2):
                yield frozenset(s1)
            else:                
                yield from recfunc(i + 1)
                s1.append(s2[ i ])
                yield from recfunc(i + 1)
                s1.pop()

        yield from recfunc()

For example below snippet works as expected:

x = SetWithSubset({1,2,3,5,6})
{2,3} in x.subsets()            # True
set() in x.subsets()            # True
x in x.subsets()                # True
x|{7} in x.subsets()            # False
set([5,3]) in x.subsets()       # True - better alternative: set([5,3]) < x
len(x.subsets())                # 32
theY4Kman
  • 5,572
  • 2
  • 32
  • 38
0

$ python -c "import itertools; a=[2,3,5,7,11]; print sum([list(itertools.combinations(a, i)) for i in range(len(a)+1)], [])" [(), (2,), (3,), (5,), (7,), (11,), (2, 3), (2, 5), (2, 7), (2, 11), (3, 5), (3, 7), (3, 11), (5, 7), (5, 11), (7, 11), (2, 3, 5), (2, 3, 7), (2, 3, 11), (2, 5, 7), (2, 5, 11), (2, 7, 11), (3, 5, 7), (3, 5, 11), (3, 7, 11), (5, 7, 11), (2, 3, 5, 7), (2, 3, 5, 11), (2, 3, 7, 11), (2, 5, 7, 11), (3, 5, 7, 11), (2, 3, 5, 7, 11)]

lidaobing
  • 1,005
  • 13
  • 26
0
>>>Set = ["A", "B","C","D"]
>>>n = 2
>>>Subsets=[[i for i,s in zip(Set, status) if int(s)  ] for status in [(format(bit,'b').zfill(len(Set))) for bit in range(2**len(Set))] if sum(map(int,status)) == n]
>>>Subsets
[['C', 'D'], ['B', 'D'], ['B', 'C'], ['A', 'D'], ['A', 'C'], ['A', 'B']]
Mohamed Moawia
  • 506
  • 4
  • 7
0

Another solution using recursion:

def subsets(nums: List[int]) -> List[List[int]]:
    n = len(nums)
    output = [[]]
    
    for num in nums:
        output += [curr + [num] for curr in output]
    
    return output

Starting from empty subset in output list. At each step we take a new integer into consideration and generates new subsets from the existing ones.

Melchia
  • 22,578
  • 22
  • 103
  • 117
0

Here is an implementation using simple recursion from first principles. Base case: if there are no elements then return the empty set. Recursive case: choose an element and return all subsets of the other elements with and without the chosen element added.

def _all_subsets(s):
    seq = list(s)
    if not seq:
        yield set([])
    else:
        choice = set([seq[0]])
        others = seq[1:]
        for without_choice in _all_subsets(others):
            yield without_choice
            yield choice | without_choice

def all_subsets(iterable):
    s = set(iterable)
    for x in _all_subsets(s):
        yield x
Aaron Watters
  • 2,784
  • 3
  • 23
  • 37