Reputation: 167
I want to define a function, which, when given a set (of positive integers) and an integer, returns the number of partitions of that integer into elements of the set. For example,
partitions({1,2,3},5)=5
Since 1+1+1+1+1=5
, 1+1+1+2=5
, 1+2+2=5
, 1+1+3=5
, and 2+3=5
.
What would be the most efficient way of implementing this in Python?
Note: It doesn't need to return the actual partitions, just the number of them.
Upvotes: 1
Views: 159
Reputation: 476813
In case the values are integral and positive (so in the set {1, 2, 3, ...}
) We can use a dynamic programming approach here:
import numpy as np
def count_subsetsum(xs : set, s : int):
v = np.zeros(s+1, dtype=int)
v[0] = 1
for x in xs:
b = np.zeros(s+1, dtype=int)
b[:] = v[:]
for d in range(x,s+1,x):
b[d:] += v[:s-d+1]
v = b
return v[s]
Here we thus consider a vector v
. Initially the vector contains only zeros, except for v[0]
that is equal to 1
(since we can construct exactly one sum with a total of zero: considering no elements at all).
Now we will update the vector. We will fetch a value x
from the iterable xs
, and we will "sum up the vector". We do this by iteratively adding v[:s-d+1]
to the end of the vector b
. v[:s-d+1]
contains all elements up to (and excluding) s-d-1
. We add this to b[d:]
. So the value of v[i]
is added to b[i+d]
for all the elements in the vector. We do this for d
in every multiple of x
. So the first upgrade we virtually "add one x
" to every element in v
. The second time, we "add a two x
s" to every sum we already constructed.
We do this for every x
in xs
, and at the end v[s]
thus contains all ways we can construct a sum that sums up to s
. So we return that v[s]
.
For example:
>>> count_subsetsum({1,2,3},5)
5 # 1+1+1+1+1 1+1+1+2 1+1+3 1+2+2 2+3
>>> count_subsetsum({1,2},5)
3 # 1+1+1+1+1 1+1+1+2 1+2+2
>>> count_subsetsum({1,3},5)
2 # 1+1+1+1+1 1+1+3
>>> count_subsetsum({1,3,5},5)
3 # 1+1+1+1+1 1+1+3 5
>>> count_subsetsum({1,2,4},5)
4 # 1+1+1+1+1 1+1+1+2 1+2+2 1+4
>>> count_subsetsum({1},5)
1 # 1+1+1+1+1
(I added a comment with the sums for every example)
The advantage of using a dynamic programming approach over a brute force approach is that it does not scale exponentially. Take for instance the following query:
>>> count_subsetsum({1,2,5,7,22},15921)
1746490921624
That is 1'746'490'921'624. Even if we manage to generate a billion results per second, it would still take 1'746 seconds (which is roughly half an hour). So usually counting is faster than enumerating.
Upvotes: 1