4
votes

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!

6
Return a tuple of the subset and whether or not the sum adds to your desired target number. Like return subset, True. - 2rs2ts
Not sure how to do that. How do I get the tuple? - Chase McCoy
Did you try to search the web for how to do that? Here's one of the top results on google. - 2rs2ts
Sorry, I do know how to return multiple values. That is not the problem. I am wondering how I can add the values that add up to the desired number to a list or tuple. Right now the function only determines True or False. I need to populate the tuple with values. - Chase McCoy
You need to make some minor effort on your own first. Take a look at the append() method of lists. - 2rs2ts

6 Answers

10
votes

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)
6
votes

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.

6
votes

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
4
votes

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.

0
votes

A bit different approach to print all subset through Recursion.

def subsetSumToK(arr,k):
    if len(arr)==0:
        if k == 0:
            return [[]]
        else:
            return []
    
    output=[]
    if arr[0]<=k: 
        temp2=subsetSumToK(arr[1:],k-arr[0])  #Including the current element 
        if len(temp2)>0:
            for i in range(len(temp2)):
                temp2[i].insert(0,arr[0])
                output.append(temp2[i])
    
    temp1=subsetSumToK(arr[1:],k)            #Excluding the current element
    if len(temp1)>0:
        for i in range(len(temp1)):
            output.append(temp1[i])
    return output

arr=[int(i) for i in input().split()]
k=int(input())
sub=subsetSumToK(arr,k)
for i in sub:
    for j in range(len(i)):
        if j==len(i)-1:
            print(i[j])
        else:
            print(i[j],end=" ")
0
votes

Rather than using recursion, you could use the iterative approach.

def desiredSum(array, sum):

  numberOfItems = len(array)
  storage = [[0 for x in range(sum + 1)] for x in range(numberOfItems + 1)]

  for i in range(numberOfItems + 1):
    for j in range(sum + 1):

        value = array[i - 1]

        if i is 0: storage[i][j] = 0
        if j is 0: storage[i][j] = 1

        if value <= j:

            noTake = storage[i - 1][j]
            take = storage[i - 1][j - value]
            storage[i][j] = noTake + take

  return storage[numberOfItems][sum]