The OP has confirmed in a comment on the question that random selections are to reflect the following procedure: "Select all combinations of distinct numbers between 3 and 10 ([3], [3,4], [3,5,6,8,9],..[9,10],[10]), throw out all combinations that don't sum to 30 and select one of those left at random". The following is a straightforward way of implementing that. Efficiency improvements could be made, but it would be a lot of work.
Code
def arrays(sum, range)
largest_sum = (range.first+range.last)*(range.last-range.first+1)/2
(raise ArgumentError,
"largest sum for range = #{largest_sum}") if sum > largest_sum
avail = [*range]
b = -(2*range.last + 1.0)
c = 8.0*sum
min_nbr = ((-b - (b*b - c)**0.5)/2).ceil.to_i
max_nbr = ((-1.0 + (1.0 + c)**0.5)/2).to_i
(min_nbr..max_nbr).each_with_object([]) { |n, a|
a.concat(avail.combination(n).select { |c| c.inject(:+) == sum }) }
end
Note min_nbr
and max_nbr
employ the quadratic formula to determine the range of the number of distinct numbers which may sum to sum
.
Examples
sum = 30
range = (3..10)
arr = arrays(sum, range) # all combinations that sum to 30
#=> [[3, 8, 9, 10], [4, 7, 9, 10], [5, 6, 9, 10], [5, 7, 8, 10],
# [6, 7, 8, 9],
# [3, 4, 5, 8, 10], [3, 4, 6, 7, 10], [3, 4, 6, 8, 9],
# [3, 5, 6, 7, 9], [4, 5, 6, 7, 8]]
(Solution time: well under 1 sec.)
10.times { p arr[rand(arr.size)] } # 10 random selections
#=> [3, 4, 6, 8, 9]
# [3, 4, 6, 8, 9]
# [5, 7, 8, 10]
# [4, 5, 6, 7, 8]
# [3, 4, 5, 8, 10]
# [6, 7, 8, 9]
# [3, 4, 5, 8, 10]
# [4, 5, 6, 7, 8]
# [6, 7, 8, 9]
# [3, 4, 6, 7, 10]
sum = 60
range = (3..10)
arr = arrays(sum, range)
#=> in `arrays': largest sum for range = 52 (ArgumentError)
Two more...
sum = 60
range = (3..20)
arr = arrays(sum, range) # all combinations that sum to 60
arr.size
#=> 1092
(Solution time: about 1 sec.)
10.times { p arr[rand(arr_size)] } # 10 random selections
#=> [12, 14, 15, 19]
# [3, 4, 6, 7, 11, 13, 16]
# [3, 6, 7, 9, 15, 20]
# [3, 8, 14, 17, 18]
# [3, 4, 5, 7, 10, 13, 18]
# [3, 5, 6, 7, 11, 13, 15]
# [5, 6, 7, 8, 14, 20]
# [4, 5, 9, 11, 15, 16]
# [4, 5, 8, 13, 14, 16]
# [3, 4, 5, 12, 16, 20]
sum = 100
range = (3..30)
arr = arrays(sum, range) # all combinations that sum to 100
arr.size
#=> 54380
(Solution time: 3 or 4 minutes)
10.times { p arr[rand(arr_size)] } # 10 random selections
#=> [3, 4, 6, 9, 11, 12, 15, 17, 23]
# [4, 5, 6, 7, 9, 13, 14, 17, 25]
# [4, 5, 6, 7, 11, 17, 21, 29]
# [9, 10, 12, 13, 17, 19, 20]
# [6, 9, 10, 23, 25, 27]
# [3, 4, 5, 6, 7, 8, 9, 14, 15, 29]
# [3, 4, 5, 6, 7, 8, 9, 15, 17, 26]
# [3, 4, 5, 6, 7, 8, 17, 22, 28]
# [3, 5, 6, 7, 9, 12, 13, 15, 30]
# [6, 8, 9, 10, 13, 15, 18, 21]