Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Subset Sum

I am trying to write a function that will not only determine whether the sum of a subset of a set adds to a desired target number, but also to print the subset that is the solution.

Here is my code for finding whether a subset exists:

def subsetsum(array,num):

    if num == 0 or num < 1:
        return False
    elif len(array) == 0:
        return False
    else:
        if array[0] == num:
            return True
        else:
            return subsetsum(array[1:],(num - array[0])) or subsetsum(array[1:],num)

How can I modify this to record the subset itself so that I can print it? Thanks in advance!

like image 875
Chase McCoy Avatar asked Apr 15 '14 15:04

Chase McCoy


People also ask

What is sum of subset problem explain with example?

Given an array of integers and a sum, the task is to have all subsets of given array with sum equal to the given sum. Example 1: Input: set[] = {4, 16, 5, 23, 12}, sum = 9. Output = true. Subset {4, 5} has the sum equal to 9.

Is subset sum NP hard?

SSP can also be regarded as an optimization problem: find a subset whose sum is at most T, and subject to that, as close as possible to T. It is NP-hard, but there are several algorithms that can solve it reasonably quickly in practice.

What is sum of subset backtracking?

Subset sum problem is to find subset of elements that are selected from a given set whose sum adds up to a given number K. We are considering the set contains non-negative values. It is assumed that the input set is unique (no duplicates are presented).


4 Answers

Based on your solution:

def subsetsum(array,num):

    if num == 0 or num < 1:
        return None
    elif len(array) == 0:
        return None
    else:
        if array[0] == num:
            return [array[0]]
        else:
            with_v = subsetsum(array[1:],(num - array[0])) 
            if with_v:
                return [array[0]] + with_v
            else:
                return subsetsum(array[1:],num)
like image 62
Samy Arous Avatar answered Oct 03 '22 02:10

Samy Arous


Slightly modified version of Samy's answer to print all possible combinations.

def subset(array, num):
    result = []
    def find(arr, num, path=()):
        if not arr:
            return
        if arr[0] == num:
            result.append(path + (arr[0],))
        else:
            find(arr[1:], num - arr[0], path + (arr[0],))
            find(arr[1:], num, path)
    find(array, num)
    return result
like image 20
harry Avatar answered Oct 03 '22 02:10

harry


You could change your approach to do that more easily, something like:

def subsetsum(array, num):
    if sum(array) == num:
        return array
    if len(array) > 1:
        for subset in (array[:-1], array[1:]):
            result = subsetsum(subset, num)
            if result is not None:
                return result

This will return either a valid subset or None.

like image 37
jonrsharpe Avatar answered Oct 03 '22 03:10

jonrsharpe


Thought I'll throw another solution into the mix.

We can map each selection of a subset of the list to a (0-padded) binary number, where a 0 means not taking the member in the corresponsing position in the list, and 1 means taking it.

So masking [1, 2, 3, 4] with 0101 creates the sub-list [2, 4].

So, by generating all 0-padded binary numbers in the range between 0 and 2^LENGTH_OF_LIST, we can iterate all selections. If we use these sub-list selections as masks and sum the selection - we can know the answer.

This is how it's done:

#!/usr/bin/env python

# use a binary number (represented as string) as a mask
def mask(lst, m):
    # pad number to create a valid selection mask 
    # according to definition in the solution laid out 
    m = m.zfill(len(lst))
    return map(lambda x: x[0], filter(lambda x: x[1] != '0', zip(lst, m)))

def subset_sum(lst, target):
    # there are 2^n binary numbers with length of the original list
    for i in xrange(2**len(lst)):
        # create the pick corresponsing to current number
        pick = mask(lst, bin(i)[2:])
        if sum(pick) == target:
            return pick
    return False


print subset_sum([1,2,3,4,5], 7)

Output:

[3, 4]

To return all possibilities we can use a generator instead (the only changes are in subset_sum, using yield instead of return and removing return False guard):

#!/usr/bin/env python

# use a binary number (represented as string) as a mask
def mask(lst, m):
    # pad number to create a valid selection mask 
    # according to definition in the solution laid out 
    m = m.zfill(len(lst))
    return map(lambda x: x[0], filter(lambda x: x[1] != '0', zip(lst, m)))

def subset_sum(lst, target):
    # there are 2^n binary numbers with length of the original list
    for i in xrange(2**len(lst)):
        # create the pick corresponsing to current number
        pick = mask(lst, bin(i)[2:])
        if sum(pick) == target:
            yield pick

# use 'list' to unpack the generator
print list(subset_sum([1,2,3,4,5], 7))

Output:

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

Note: While not padding the mask with zeros may work as well, as it will simply select members of the original list in a reverse order - I haven't checked it and didn't use it.

I didn't use it since it's less obvious (to me) what's going on with such trenary-like mask (1, 0 or nothing) and I rather have everything well defined.

like image 29
Reut Sharabani Avatar answered Oct 03 '22 04:10

Reut Sharabani