简体   繁体   English

Python 子集和

[英]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.我正在尝试编写一个 function ,它不仅可以确定集合的子集的总和是否添加到所需的目标数,还可以打印作为解决方案的子集。

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!提前致谢!

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)

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 .这将返回一个有效的子集或None

Slightly modified version of Samy's answer to print all possible combinations. Samy 的答案的稍微修改版本以打印所有可能的组合。

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

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.我们可以将列表子集的每个选择映射到一个(0 填充的)二进制数,其中 0 表示不占用列表中相应位置的成员,1 表示占用它。

So masking [1, 2, 3, 4] with 0101 creates the sub-list [2, 4] .所以用0101屏蔽[1, 2, 3, 4]会创建子列表[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.因此,通过生成 0 到 2^LENGTH_OF_LIST 范围内的所有 0 填充二进制数,我们可以迭代所有选择。 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):为了返回所有可能性,我们可以使用生成器代替(唯一的变化是subset_sum ,使用yield而不是return并删除return False守卫):

#!/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.我没有使用它,因为它不太明显(对我来说)这种类似 trenary 的掩码(1、0 或什么都没有)发生了什么,我宁愿让一切都得到很好的定义。

A bit different approach to print all subset through Recursion .通过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=" ")

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]

Slightly updated the below code to return all possible combinations for this problem.稍微更新了以下代码以返回此问题的所有可能组合。 Snippet in the thread above will not print all possible combinations when the input is given as subset([4,3,1],4)当输入作为子集([4,3,1],4)给出时,上面线程中的片段将不会打印所有可能的组合

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM