One could count the number of ways to make each possible total using two spans, four spans, eight spans, and so forth (where a span is a range of integers including its endpoints). With those numbers tabulated, you can work backwards toward a sample. For example, suppose there are 16 spans, each including numbers 1 to 9. One finds there are w = 202752772954792 ways to make a total of t = 100
. Choose a random number r in the range 1 to w. Search (or lookup) to find a number J such that the sum with J>j of leftways(t-j)*rightways(j))
exceeds r and the sum with J>j+1 does not, where leftways(i)
is the number of ways of making i using the first 8 spans, and rightways(j)
is the number of ways of making j using the last 8 spans. Recurse using i = t-j with the first 8 spans and j with the last 8, etc. Base cases occur whenever there is only one way of making a required total.
The code below can be revised to run more efficiently by sorting the spans into descending order by width (that is, list the widest spans first) and removing some swaps. Note that if spans are not in descending order by width, the result vector will not be in the same order as the spans. Also, it might be possible to replace the linear for j ...
search in findTarget
by a binary search but I haven't figured out how to do so without using quite a bit more space.
The code could be made cleaner and more clear by using objects to store the tree structures, instead of just tuples.
Space used probably is O(s²·(lg m))
if there are m spans whose maxima total up to s. Time used is O(s²·(lg m))
for the initial tabulation of sums of products and probably O(m+(lg m)·(s/m))
or O(m+(lg m)·s)
for each sample. (I haven't properly analyzed space and time requirements.) On a 2GHz machine, the code as shown produces about 8000 samples per second if there are 16 identical spans 1...10; about 5000 samples per second if there are 32 identical spans 1...3; and about 2000 samples per second if there are 32 identical spans 1...30. Some sample outputs for various target totals and sets of spans are shown after the code.
from random import randrange
def randx(hi): # Return a random integer from 1 to hi
return 1+randrange(hi) if hi>0 else 0
# Compute and return c with each cell k set equal to
# sum of products a[k-j] * b[j], taken over all relevant j
def sumprods(lt, rt):
a, b = lt[0], rt[0]
(la,ma,aa), (lb,mb,bb) = a, b
if ma-la < mb-lb: # Swap so |A| >= |B|
la, ma, aa, lb, mb, bb = lb, mb, bb, la, ma, aa
lc, mc = la+lb, ma+mb
counts = [0]*(mc+1)
for k in range(lc,mc+1):
for j in range(max(lb,k-ma), min(mb,k-la)+1):
counts[k] += aa[k-j] * bb[j]
return (lc, mc, counts)
def maketree(v):
lv = len(v)
if lv<2:
return (v[0], None, None)
ltree = maketree(v[:lv/2])
rtree = maketree(v[lv/2:])
return (sumprods(ltree, rtree), ltree, rtree)
def findTarget(tototal, target, tree):
global result
lt, rt = tree[1], tree[2]
# Put smaller-range tree second
if lt[0][1]-lt[0][0] < rt[0][1]-rt[0][0]: lt, rt = rt, lt
(la,ma,aa), (lb,mb,bb) = lt[0], rt[0]
lc, mc = la+lb, ma+mb
count = 0
for j in range(max(lb,tototal-ma), min(mb,tototal-la)+1):
i = tototal-j
count += aa[i] * bb[j]
if count >= target: break
# Suppose that any way of getting i in left tree is ok
if lt[1]: findTarget(i, randx(aa[i]), lt)
else: result += [i]
# Suppose that any way of getting j in right tree is ok
if rt[1]: findTarget(j, randx(bb[j]), rt)
else: result += [j]
spans, ttotal, tries = [(1,6), (5,11), (2,9), (3,7), (4,9), (4,12), (5,8),
(3,5), (2,9), (3,11), (3,9), (4,5), (5,9), (6,13),
(7,8), (4,11)], 100, 10
spans, ttotal, tries = [(10*i,10*i+9) for i in range(16)], 1300, 10000
spans, ttotal, tries = [(1,3) for i in range(32)], 64, 10000
spans, ttotal, tries = [(1,10) for i in range(16)], 100, 10
print 'spans=', spans
vals = [(p, q, [int(i>=p) for i in range(q+1)]) for (p,q) in spans]
tree = maketree(vals)
nways = tree[0][2][ttotal]
print 'nways({}) = {}'.format(ttotal, nways)
for i in range(1,tries):
result = []
findTarget(ttotal, randx(nways), tree)
print '{:2}: {} {}'.format(i, sum(result), result)
In the output samples shown below, the lines with colons contain a sample number, a sample-total, and a sample-values array. Other lines show the set of spans and the number of ways of making a desired total.
spans= [(1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10)]
nways(100) = 202752772954792
1: 100 [8, 9, 1, 2, 8, 1, 10, 6, 6, 3, 6, 10, 6, 10, 10, 4]
2: 100 [2, 6, 10, 3, 1, 10, 9, 5, 10, 6, 2, 10, 9, 7, 4, 6]
3: 100 [1, 6, 5, 1, 9, 10, 10, 7, 10, 2, 8, 9, 10, 9, 2, 1]
4: 100 [10, 7, 6, 3, 7, 8, 6, 5, 7, 7, 5, 1, 9, 6, 9, 4]
5: 100 [7, 1, 10, 5, 5, 4, 9, 5, 3, 9, 2, 8, 6, 8, 10, 8]
spans= [(1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3)]
nways(64) = 159114492071763
1: 64 [2, 2, 1, 3, 1, 2, 2, 2, 1, 2, 3, 3, 3, 2, 2, 1, 2, 3, 1, 3, 1, 3, 2, 1, 2, 3, 2, 2, 1, 2, 2, 2]
2: 64 [1, 2, 1, 1, 1, 3, 3, 3, 2, 1, 1, 2, 3, 2, 2, 3, 3, 3, 1, 2, 1, 2, 2, 3, 2, 2, 1, 3, 1, 3, 2, 2]
3: 64 [2, 1, 3, 2, 3, 3, 1, 3, 1, 3, 2, 2, 1, 2, 1, 3, 1, 3, 1, 2, 2, 2, 2, 1, 1, 3, 2, 2, 3, 2, 3, 1]
4: 64 [2, 3, 3, 2, 3, 2, 1, 3, 2, 2, 1, 2, 1, 1, 3, 2, 2, 3, 3, 1, 1, 2, 2, 1, 1, 2, 3, 3, 2, 1, 1, 3]
5: 64 [1, 1, 1, 3, 2, 2, 3, 2, 2, 1, 2, 2, 1, 2, 1, 1, 3, 3, 2, 3, 1, 2, 2, 3, 3, 3, 2, 2, 1, 3, 3, 1]
spans= [(0, 9), (10, 19), (20, 29), (30, 39), (40, 49), (50, 59), (60, 69), (70, 79), (80, 89), (90, 99), (100, 109), (110, 119), (120, 129), (130, 139), (140, 149), (150, 159)]
nways(1323) = 5444285920
1: 1323 [8, 19, 25, 35, 49, 59, 69, 76, 85, 99, 108, 119, 129, 139, 148, 156]
2: 1323 [8, 16, 29, 39, 48, 59, 69, 77, 88, 95, 109, 119, 129, 138, 147, 153]
3: 1323 [9, 16, 28, 39, 49, 58, 69, 79, 87, 96, 106, 115, 128, 138, 149, 157]
4: 1323 [8, 17, 29, 36, 45, 58, 69, 78, 89, 99, 106, 119, 128, 135, 149, 158]
5: 1323 [9, 16, 27, 34, 48, 57, 69, 79, 88, 99, 109, 119, 128, 139, 144, 158]
spans= [(1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (
1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (
1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (
1, 30), (1, 30), (1, 30)]
nways(640) = 19144856039395888221416547336829636235610525
1: 640 [7, 24, 27, 9, 30, 23, 30, 27, 28, 29, 2, 30, 28, 19, 7, 27, 10, 2, 21, 23, 24, 2
7, 24, 16, 29, 8, 13, 23, 2, 19, 27, 25]
2: 640 [30, 2, 17, 28, 30, 16, 5, 1, 26, 24, 22, 19, 26, 16, 16, 30, 27, 15, 19, 30, 15,
30, 22, 5, 30, 9, 13, 25, 19, 15, 30, 28]
3: 640 [2, 24, 1, 23, 20, 5, 30, 22, 24, 19, 22, 9, 28, 29, 5, 24, 14, 30, 24, 16, 26, 2
1, 26, 20, 20, 19, 24, 29, 24, 8, 23, 29]
4: 640 [7, 20, 16, 24, 22, 14, 28, 28, 26, 8, 21, 9, 22, 24, 28, 19, 5, 13, 9, 24, 25, 2
2, 29, 18, 20, 21, 17, 26, 30, 9, 26, 30]