Given integers how do I find asc and desc sequences of three? - ruby

I have integers i.e. 9, 5, 4, 3, 1, 6, 7, 8. I want to return the index where a sequence of three descending or ascending integers exists. In the example above I would get indices 1 and 5. What is the ruby code for this?
def seq
array = [9,5,4,3,1,6,7,8]
array.each_with_index |val, index|
if (val < (array[index + 1]).val < (array[index + 1]).val)
puts "#{index}"
# Skip two indexes
end
end

I think the logic behind your solution is almost correct, but your syntax is pretty far off from valid Ruby.
Here are a pair of pretty verbose solutions that will (hopefully) be fairly obvious:
numbers = [9, 6, 5, 4, 3, 1, 6, 7, 8]
# Find non-overlapping sets
i = 0
until i > numbers.length - 2
a, b, c = numbers[i..i + 2]
if (a - b == b - c) && (a - b).abs == 1
puts "#{i} (#{a},#{b},#{c})"
# Skip next two indexes
i += 3
else
i += 1
end
end
# Find overlapping sets (same solution, but don't skip indexes)
(0...numbers.length - 2).each do |i|
a, b, c = numbers[i..i + 2]
if (a - b == b - c) && (a - b).abs == 1
puts "#{i} (#{a},#{b},#{c})"
end
end

Since the question is not clear enough. I will assume the question is about finding 3 ascending or descending continuous numbers. If the length of the satisfied sequence it longer than 3, e.g [2, 3, 4, 5], it returns 0 and 1.
Here is the algorithm, do list[index] - list[index - 1] for all elements, and repeat it for another time, the answer will be the index of 0 elements after the calculation.
Intuitively,
original 9, 5, 4, 3, 1, 6, 7, 8
first pass -4, -1, -1, -2, 5, 1, 1
2nd pass 3, 0, -1, 7, 4, 0 -> the answer will be the indexes of 0's, which is 1, 5
Algorithm:
lst = [9, 5, 4, 3, 1, 6, 7, 8]
lst1 = lst.each_cons(2).map{ |a, b| b-a }
lst2 = lst1.each_cons(2).map{ |a, b| b-a }
result = lst2.each_index.select{|i| lst2[i] == 0}
result = [1, 5]

Here’s a solution using each_cons(3).with_index:
[9,5,4,3,1,6,7,8].each_cons(3).with_index.select { |s, i| s[0] < s[1] && s[1] < s[2] }.map(&:last)

Related

Why does my algorithm solution fail validation?

Here is a task i got:
Cards are laid out on the table in a row, each card has a natural number written on it. In one move, it is allowed to take a card either from the left or from the right end of the row. In total, you can make k moves. The final score is equal to the sum of the numbers on the selected cards. Determine what is the maximum score you can get at the end of the game.
Here`s my code:
def card_counter(arr, k):
if len(arr) == k:
return sum(arr)
rang = len(arr) // 2
left = arr[:rang]
right = list(reversed(arr[rang:]))
c = 0
for _ in range(k):
min_arr = left if sum(left) >= sum(
right) and len(left) > 0 else right
c += min_arr.pop(0)
return c
if __name__ == '__main__':
assert card_counter([1, 2, 3, 4, 5], 5) == 15
assert card_counter([0, 0, 0], 1) == 0
assert card_counter([150], 1) == 150
This code works on all variants that I have come up with, including extreme cases. But the system does not accept this option, automatic tests do not pass. Where can there be a mistake?
I cannot comment on the algorithm you implemented as you did not state it in non-algorithm terms and I am not familiar with the language you are using (which I am guessing is Python). I will present a simple solution written in Ruby, hoping that the description I give will make it understood by readers who do not know Ruby.
Suppose
deck = [1, 2, 5, 7, 1, 4, 6, 3]
nbr_moves = 4
ds = deck.size
#=> 8
After removing nbr_moves from the ends,
m = ds - nbr_moves
#=> 4
consecutive cards will remain. In Ruby we could write
best = (0..ds-1).each_cons(m).max_by { |arr| deck.values_at(*arr).sum }
#=> [3, 4, 5, 6]
to obtain the indices of deck, for which the sum of the associated values is maximum:
deck.values_at(*best).sum
#=> 18
where
deck.values_at(*best)
#=> [7, 1, 4, 6]
In view of the value of best ([3, 4, 5, 6]), we need to remove left = best.first #=> 3 elements from the left and nbr_moves - left #=> 1 element from the right. The order of the removals is not relevant.
Note that
enum = (0..ds-1).each_cons(m)
#=> #<Enumerator: 0..7:each_cons(4)>
returns an enumerator. We can convert this enumerator to an array to see the values it will generate.
enum.to_a
#=> [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7]]
When, for example,
arr = [2, 3, 4, 5]
then
a = deck.values_at(*arr)
#=> [5, 7, 1, 4]
a.sum
#=> 17
Note that, having computed, for example,
t = [deck[i], deck[i+1],..., deck[j]].sum
the sum of
[deck[i+1], deck[i+2],..., deck[j+1]]
is seen to equal
t - deck[i] + deck[j+1]
This suggests a more efficient way to perform the calculations when m is large.

ruby method returns wrong elements with odd/even index ruby 2.6.0

def even_odd_array
number = 5169294814153321
array_odd_index = []
array_even_index = []
array_of_chars = number.to_s.chars.map(&:to_i)
array_of_chars.each { |x| array_of_chars.index(x) % 2 == 0 ? array_even_index << x : array_odd_index << x } <----- this returns wrong arrays
#array_odd_index, array_even_index = array_of_chars.each_slice(2).to_a.transpose
p array_even_index
p array_odd_index
end
array_even_index [5, 6, 2, 4, 4, 5, 3, 3, 2]
array_odd_index [1, 9, 9, 8, 1, 1, 1]
what's wrong with it and are there any other ways to make it?
The problem with your actual code is that index returns the index of the first element it finds in the receiver. As 1 is 4 times in number it'll return the index of the first 1 in number from left to right, same for all other repeated numbers.
An easy solution; use each_with_index which allows you to iterate over each element in the receiver plus yielding the current index of that element, so you can check if the index is even or not, deciding where to push the element:
array_of_chars.each_with_index do |x, index|
if index.even?
array_even_index << x
else
array_odd_index << x
end
end
Or you can use partition plus with_index for that:
array_even_index, array_odd_index = 5169294814153321.digits.reverse.partition.with_index { |_, index| index.even? }
p array_even_index # [5, 6, 2, 4, 1, 1, 3, 2]
p array_odd_index # [1, 9, 9, 8, 4, 5, 3, 1]

Find all combinations in a 3x3 matrix following some rules

Given a 3x3 matrix:
|1 2 3|
|4 5 6|
|7 8 9|
I'd like to calculate all the combinations by connecting the numbers in this matrix following these rules:
the combinations width are between 3 and 9
use one number only once
you can only connect adjacent numbers
Some examples: 123, 258, 2589, 123654, etc.
For example 1238 is not a good combination because 3 and 8 are not adjacent. The 123 and the 321 combination is not the same.
I hope my description is clear.
If anyone has any ideas please let me know. Actually I don't know how to start :D. Thanks
This is a search problem. You can just use straightforward depth-first-search with recursive programming to quickly solve the problem. Something like the following:
func search(matrix[N][M], x, y, digitsUsed[10], combination[L]) {
if length(combination) between 3 and 9 {
add this combination into your solution
}
// four adjacent directions to be attempted
dx = {1,0,0,-1}
dy = {0,1,-1,0}
for i = 0; i < 4; i++ {
next_x = x + dx[i]
next_y = y + dy[i]
if in_matrix(next_x, next_y) and not digitsUsed[matrix[next_x][next_y]] {
digitsUsed[matrix[next_x][next_y]] = true
combination += matrix[next_x][next_y]
search(matrix, next_x, next_y, digitsUsed, combination)
// At this time, sub-search starts with (next_x, next_y) has been completed.
digitsUsed[matrix[next_x][next_y]] = false
}
}
}
So you could run search function for every single grid in the matrix, and every combinations in your solution are different from each other because they start from different grids.
In addition, we don't need to record the status which indicates one grid in the matrix has or has not been traversed because every digit can be used only once, so grids which have been traversed will never be traversed again since their digits have been already contained in the combination.
Here is a possible implementation in Python 3 as a a recursive depth-first exploration:
def find_combinations(data, min_length, max_length):
# Matrix of booleans indicating what values have been used
visited = [[False for _ in row] for row in data]
# Current combination
comb = []
# Start recursive algorithm at every possible position
for i in range(len(data)):
for j in range(len(data[i])):
# Add initial combination element and mark as visited
comb.append(data[i][j])
visited[i][j] = True
# Start recursive algorithm
yield from find_combinations_rec(data, min_length, max_length, visited, comb, i, j)
# After all combinations with current element have been produced remove it
visited[i][j] = False
comb.pop()
def find_combinations_rec(data, min_length, max_length, visited, comb, i, j):
# Yield the current combination if it has the right size
if min_length <= len(comb) <= max_length:
yield comb.copy()
# Stop the recursion after reaching maximum length
if len(comb) >= max_length:
return
# For each neighbor of the last added element
for i2, j2 in ((i - 1, j), (i, j - 1), (i, j + 1), (i + 1, j)):
# Check the neighbor is valid and not visited
if i2 < 0 or i2 >= len(data) or j2 < 0 or j2 >= len(data[i2]) or visited[i2][j2]:
continue
# Add neighbor and mark as visited
comb.append(data[i2][j2])
visited[i2][j2] = True
# Produce combinations for current starting sequence
yield from find_combinations_rec(data, min_length, max_length, visited, comb, i2, j2)
# Remove last added combination element
visited[i2][j2] = False
comb.pop()
# Try it
data = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
min_length = 3
max_length = 9
for comb in find_combinations(data, min_length, max_length):
print(c)
Output:
[1, 2, 3]
[1, 2, 3, 6]
[1, 2, 3, 6, 5]
[1, 2, 3, 6, 5, 4]
[1, 2, 3, 6, 5, 4, 7]
[1, 2, 3, 6, 5, 4, 7, 8]
[1, 2, 3, 6, 5, 4, 7, 8, 9]
[1, 2, 3, 6, 5, 8]
[1, 2, 3, 6, 5, 8, 7]
[1, 2, 3, 6, 5, 8, 7, 4]
[1, 2, 3, 6, 5, 8, 9]
[1, 2, 3, 6, 9]
[1, 2, 3, 6, 9, 8]
[1, 2, 3, 6, 9, 8, 5]
[1, 2, 3, 6, 9, 8, 5, 4]
[1, 2, 3, 6, 9, 8, 5, 4, 7]
...
Look at all the combinations and take the connected ones:
import itertools
def coords(n):
"""Coordinates of number n in the matrix."""
return (n - 1) // 3, (n - 1) % 3
def adjacent(a, b):
"""Check if a and b are adjacent in the matrix."""
ai, aj = coords(a)
bi, bj = coords(b)
return abs(ai - bi) + abs(aj - bj) == 1
def connected(comb):
"""Check if combination is connected."""
return all(adjacent(a, b) for a, b in zip(comb, comb[1:]))
for width in range(3, 10):
for comb in itertools.permutations(range(1, 10), width):
if connected(comb):
print(comb)

Generating combinations from an array which == a specified amount?

I need to get all the possible number combinations from denom_arr which equal the amt.
denom_arr = [4,3,1]
amt = 10
This case would produce:
[4, 4, 1, 1]
[3, 3, 3, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[4, 3, 1, 1, 1]
[4, 3, 3]
. . . (other cases...)
Problem is the code I wrote is breaking after 1-3 and I'm not sure how to make it loop over the same index to get case 4-6+
set, sets = [], []
i = 0
loop do
i = 0 if denom_arr[i].nil?
loop do
set << denom_arr[i]
break if set.inject(:+) > amt
end
set.pop if set.inject(:+) > amt
if set.inject(:+) == amt
sets << set
set = []
denom_arr.shift
end
i += 1
sets
break if denom_arr.empty?
end
UPDATE
I know this can be done with recursion with memoization/dynamic programming techniques, but I am trying to do this strictly in a loop for the sake of testing a theory.
I would do this recursively
def possible_sums(arr, amt)
return [[]] if amt == 0
return [] if amt < 0
arr.reduce([]) do |sums, e|
sums.concat(
possible_sums(arr, amt-e)
.map { |sum| sum.unshift(e).sort }
)
end.uniq
end
p possible_sums([4,3,1], 10)
# => [
# [1, 1, 4, 4], [3, 3, 4], [1, 1, 1, 3, 4], [1, 1, 1, 1, 1, 1, 4],
# [1, 3, 3, 3], [1, 1, 1, 1, 3, 3], [1, 1, 1, 1, 1, 1, 1, 3],
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
# ]
Although this is potentially inefficient in that it repeats work, this can be alleviated by using dynamic programming (essentially, memoizing the results of the recursive function).
UPDATE Here is an iterative solution:
def possible_sums_it(arr, amt)
sums = Array.new(amt+1) { [] }
sums[0] << []
(1..amt).each do |i|
arr.each do |e|
if i-e >= 0
sums[i].concat(
sums[i-e].map { |s| [e, *s].sort }
)
end
end
sums[i].uniq!
end
sums[amt]
end
This is in fact the dynamic programming algorithm for the problem.
So if you squint at it just right, you'll see that essentially what it is doing, is calculating all the possible sums for 0 up to amt into the sums array, using what is basically the recursive algorithm, but instead of the recursive call, we lookup a value in sums that we have calculated beforehand.
This works because we know that we won't need sums[i] before sums[j] for j < i.

Number of possible equations of K numbers whose sum is N in ruby

I have to create a program in ruby on rails so that it will take less time to solve the particular condition. Now i am to getting the less response time for k=4 but response time is more in case of k>5
Problem:
Problem is response time.
When value of k is more than 5 (k>5) response time is too late for given below equation.
Input: K, N (where 0 < N < ∞, 0 < K < ∞, and K <= N)
Output: Number of possible equations of K numbers whose sum is N.
Example Input:
N=10 K=3
Example Output:
Total unique equations = 8
1 + 1 + 8 = 10
1 + 2 + 7 = 10
1 + 3 + 6 = 10
1 + 4 + 5 = 10
2 + 2 + 6 = 10
2 + 3 + 5 = 10
2 + 4 + 4 = 10
3 + 3 + 4 = 10
For reference, N=100, K=3 should have a result of 833 unique sets
Here is my ruby code
module Combination
module Pairs
class Equation
def initialize(params)
#arr=[]
#n = params[:n]
#k = params[:k]
end
#To create possible equations
def create_equations
return "Please Enter value of n and k" if #k.blank? && #n.blank?
begin
Integer(#k)
rescue
return "Error: Please enter any +ve integer value of k"
end
begin
Integer(#n)
rescue
return "Error: Please enter any +ve integer value of n"
end
return "Please enter k < n" if #n < #k
create_equations_sum
end
def create_equations_sum
aar = []
#arr = []
#list_elements=(1..#n).to_a
(1..#k-1).each do |i|
aar << [*0..#n-1]
end
traverse([], aar, 0)
return #arr.uniq #return result
end
#To check sum
def generate_sum(*args)
new_elements = []
total= 0
args.flatten.each do |arg|
total += #list_elements[arg]
new_elements << #list_elements[arg]
end
if total < #n
new_elements << #n - total
#arr << new_elements.sort
else
return
end
end
def innerloop(arrayOfCurrentValues)
generate_sum(arrayOfCurrentValues)
end
#Recursive method to create dynamic nested loops.
def traverse(accumulated,params, index)
if (index==params.size)
return innerloop(accumulated)
end
currentParam = params[index]
currentParam.each do |currentElementOfCurrentParam|
traverse(accumulated+[currentElementOfCurrentParam],params, index+1)
end
end
end
end
end
run the code using
params = {:n =>100, :k =>4}
c = Combination::Pairs::Equation.new(params)
c.create_equations
Here are two ways to compute your answer. The first is simple but not very efficient; the second, which relies on an optimization technique, is much faster, but requires considerably more code.
Compact but Inefficient
This is a compact way to do the calculation, making use of the method Array#repeated_combination:
Code
def combos(n,k)
[*(1..n-k+1)].repeated_combination(3).select { |a| a.reduce(:+) == n }
end
Examples
combos(10,3)
#=> [[1, 1, 8], [1, 2, 7], [1, 3, 6], [1, 4, 5],
# [2, 2, 6], [2, 3, 5], [2, 4, 4], [3, 3, 4]]
combos(100,4).size
#=> 832
combos(1000,3).size
#=> 83333
Comment
The first two calculations take well under one second, but the third took a couple of minutes.
More efficient, but increased complexity
Code
def combos(n,k)
return nil if k.zero?
return [n] if k==1
return [1]*k if k==n
h = (1..k-1).each_with_object({}) { |i,h| h[i]=[[1]*i] }
(2..n-k+1).each do |i|
g = (1..[n/i,k].min).each_with_object(Hash.new {|h,k| h[k]=[]}) do |m,f|
im = [i]*m
mxi = m*i
if m==k
f[mxi].concat(im) if mxi==n
else
f[mxi] << im if mxi + (k-m)*(i+1) <= n
(1..[(i-1)*(k-m), n-mxi].min).each do |j|
h[j].each do |a|
f[mxi+j].concat([a+im]) if
((a.size==k-m && mxi+j==n) ||
(a.size<k-m && (mxi+j+(k-m-a.size)*(i+1))<=n))
end
end
end
end
g.update({ n=>[[i]*k] }) if i*k == n
h.update(g) { |k,ov,nv| ov+nv }
end
h[n]
end
Examples
p combos(10,3)
#=> [[3, 3, 4], [2, 4, 4], [2, 3, 5], [1, 4, 5],
# [2, 2, 6], [1, 3, 6], [1, 2, 7], [1, 1, 8]]
p combos(10,4)
#=> [[2, 2, 3, 3], [1, 3, 3, 3], [2, 2, 2, 4], [1, 2, 3, 4], [1, 1, 4, 4],
# [1, 2, 2, 5], [1, 1, 3, 5], [1, 1, 2, 6], [1, 1, 1, 7]]
puts "size=#{combos(100 ,3).size}" #=> 833
puts "size=#{combos(100 ,5).size}" #=> 38224
puts "size=#{combos(1000,3).size}" #=> 83333
Comment
The calculation combos(1000,3).size took about five seconds, the others were all well under one second.
Explanation
This method employs dynamic programming to compute a solution. The state variable is the largest positive integer used to compute arrays with sizes no more than k whose elements sum to no more than n. Begin with the largest integer equal to one. The next step is compute all combinations of k or fewer elements that include the numbers 1 and 2, then 1, 2 and 3, and so on, until we have all combinations of k or fewer elements that include the numbers 1 through n. We then select all combinations of k elements that sum to n from the last calculation.
Suppose
k => 3
n => 7
then
h = (1..k-1).each_with_object({}) { |i,h| h[i]=[[1]*i] }
#=> (1..2).each_with_object({}) { |i,h| h[i]=[[1]*i] }
#=> { 1=>[[1]], 2=>[[1,1]] }
This reads, using the only the number 1, [[1]] is the array of all arrays that sum to 1 and [[1,1]] is the array of all arrays that sum to 2.
Notice that this does not include the element 3=>[[1,1,1]]. That's because, already having k=3 elments, if cannot be combined with any other elements, and sums to 3 < 7.
We next execute:
enum = (2..n-k+1).each #=> #<Enumerator: 2..5:each>
We can convert this enumerator to an array to see what values it will pass into its block:
enum.to_a #=> [2, 3, 4, 5]
As n => 7 you may be wondering why this array ends at 5. That's because there are no arrays containing three positive integers, of which at least one is a 6 or a 7, whose elements sum to 7.
The first value enum passes into the block, which is represented by the block variable i, is 2. We will now compute a hash g that includes all arrays that sum to n => 7 or less, have at most k => 3 elements, include one or more 2's and zero or more 1's. (That's a bit of a mouthful, but it's still not precise, as I will explain.)
enum2 = (1..[n/i,k].min).each_with_object(Hash.new {|h,k| h[k]=[]})
#=> (1..[7/2,3].min).each_with_object(Hash.new {|h,k| h[k]=[]})
#=> (1..3).each_with_object(Hash.new {|h,k| h[k]=[]})
Enumerable#each_with_object creates an initially-empty hash that is represented by the block variable f. The default value of this hash is such that:
f[k] << o
is equivalent to
(f[k] |= []) << o
meaning that if f does not have a key k,
f[k] = []
is executed before
f[k] << o
is performed.
enum2 will pass the following elements into its block:
enum2.to_a #=> => [[1, {}], [2, {}], [3, {}]]
(though the hash may not be empty when elements after the first are passed into the block). The first element passed to the block is [1, {}], represented by the block variables:
m => 1
f => Hash.new {|h,k| h[k]=[]}
m => 1 means we will intially construct arrays that contain one (i=) 2.
im = [i]*m #=> [2]*1 => [2]
mxi = m*i #=> 2*1 => 2
As (m == k) #=> (1 == 3) => false, we next execute
f[mxi] << im if mxi + (k-m)*(i+1) <= n
#=> f[2] << [2] if 2 + (3-1)*(1+1) <= 7
#=> f[2] << [2] if 8 <= 7
This considers whether [2] should be added to f[2] without adding any integers j < i = 2. (We have yet to consider the combining of one 2 with integers less than 2 [i.e., 1].) As 8 <= 7, we do not add [2] to f[2]. The reason is that, for this to be part of an array of length k=3, it would be of the form [2,x,y], where x > 2 and y > 2, so 2+x+y >= 2+3+3 = 8 > n = 7. Clear as mud?
Next,
enum3 = (1..[(i-1)*(k-m), n-mxi].min).each
#=> = (1..[2,5].min).each
#=> = (1..2).each
#=> #<Enumerator: 1..2:each>
which passes the values
enum3.to_a #=> [1, 2]
into its block, represented by the block variable j, which is the key of the hash h. What we will be doing here is combine one 2 (m=1) with arrays of elements containing integers up to 1 (i.e., just 1) that sum to j, so the elements of the resulting array will sum to m * i + j => 1 * 2 + j => 2 + j.
The reason enum3 does not pass values of j greater than 2 into its block is that h[l] is empty for l > 2 (but its a little more complicated when i > 2).
For j => 1,
h[j] #=> [[1]]
enum4 = h[j].each #=> #<Enumerator: [[1]]:each>
enum4.to_a #=> [[1]]
a #=> [1]
so
f[mxi+j].concat([a+im]) if
((a.size==k-m && mxi+j==n) || (a.size<k-m && (mxi+j+(k-m-a.size)*(i+1))<=n))
#=> f[2+1].concat([[1]+[2]) if ((1==2 && 2+1==7) || (1<=3-1 && (2+1+(1)*(3)<=7))
#=> f[3].concat([1,2]) if ((false && false) || (1<=2 && (6<=7))
#=> f[3] = [] << [[1,2]] if (false || (true && true)
#=> f[3] = [[1,2]] if true
So the expression on the left is evaluated. Again, the conditional expressions are a little complex. Consider first:
a.size==k-m && mxi+j==n
which is equivalent to:
([2] + f[j]).size == k && ([2] + f[j]).reduce(:+) == n
That is, include the array [2] + f[j] if it has k elements that sum to n.
The second condition considers whether the array the arrays [2] + f[j] with fewer than k elements can be "completed" with integers l > i = 2 and have a sum of n or less.
Now, f #=> {3=>[[1, 2]]}.
We now increment j to 2 and consider arrays [2] + h[2], whose elements will total 4.
For j => 2,
h[j] #=> [[1, 1]]
enum4 = h[j].each #=> #<Enumerator: [[1, 1]]:each>
enum4.to_a #=> [[1, 1]]
a #=> [1, 1]
f[mxi+j].concat([a+im]) if
((a.size==k-m && mxi+j==n) || (a.size<k-m && (mxi+j+(k-m-a.size)*(i+1)<=n))
#=> f[4].concat([1, 1, 2]) if ((2==(3-1) && 2+2 == 7) || (2+2+(3-1-2)*(3)<=7))
#=> f[4].concat([1, 1, 2]) if (true && false) || (false && true))
#=> f[4].concat([1, 1, 2]) if false
so this operation is not performed (since [1,1,2].size => 3 = k and [1,1,2].reduce(:+) => 4 < 7 = n.
We now increment m to 2, meaning that we will construct arrays having two (i=) 2's. After doing so, we see that:
f={3=>[[1, 2]], 4=>[[2, 2]]}
and no other arrays are added when m => 3, so we have:
g #=> {3=>[[1, 2]], 4=>[[2, 2]]}
The statement
g.update({ n=>[i]*k }) if i*k == n
#=> g.update({ 7=>[2,2,2] }) if 6 == 7
adds the element 7=>[2,2,2] to the hash g if the sum of its elements equals n, which it does not.
We now fold g into h, using Hash#update (aka Hash#merge!):
h.update(g) { |k,ov,nv| ov+nv }
#=> {}.update({3=>[[1, 2]], 4=>[[2, 2]]} { |k,ov,nv| ov+nv }
#=> {1=>[[1]], 2=>[[1, 1]], 3=>[[1, 2]], 4=>[[2, 2]]}
Now h contains all the arrays (values) whose keys are the array totals, comprised of the integers 1 and 2, which have at most 3 elements and sum to at most 7, excluding those arrays with fewer than 3 elements which cannot sum to 7 when integers greater than two are added.
The operations performed are as follows:
i m j f
h #=> { 1=>[[1]], 2=>[[1,1]] }
2 1 1 {3=>[[1, 2]]}
2 1 2 {3=>[[1, 2]]}
2 2 1 {3=>[[1, 2]], 4=>[[2, 2]]}
{3=>[[1, 2]], 4=>[[2, 2]]}
3 1 1 {}
3 1 2 {}
3 1 3 {}
3 1 4 {7=>[[2, 2, 3]]}
3 2 1 {7=>[[2, 2, 3], [1, 3, 3]]}
g before g.update: {7=>[[2, 2, 3], [1, 3, 3]]}
g after g.update: {7=>[[2, 2, 3], [1, 3, 3]]}
h after h.update(g): {1=>[[1]],
2=>[[1, 1]],
3=>[[1, 2]],
4=>[[2, 2]],
7=>[[2, 2, 3], [1, 3, 3]]}
4 1 1 {}
4 1 2 {}
4 1 3 {7=>[[1, 2, 4]]}
g before g.update: {7=>[[1, 2, 4]]}
g after g.update: {7=>[[1, 2, 4]]}
h after h.update(g): {1=>[[1]],
2=>[[1, 1]],
3=>[[1, 2]],
4=>[[2, 2]],
7=>[[2, 2, 3], [1, 3, 3], [1, 2, 4]]}
5 1 1 {}
5 1 2 {7=>[[1, 1, 5]]}
g before g.update: {7=>[[1, 1, 5]]}
g after g.update: {7=>[[1, 1, 5]]}
h after h.update(g): {1=>[[1]],
2=>[[1, 1]],
3=>[[1, 2]],
4=>[[2, 2]],
7=>[[2, 2, 3], [1, 3, 3], [1, 2, 4], [1, 1, 5]]}
And lastly,
h[n].select { |a| a.size == k }
#=> h[7].select { |a| a.size == 3 }
#=> [[2, 2, 3], [1, 3, 3], [1, 2, 4], [1, 1, 5]]
#Cary's answer is very in-depth and impressive, but it appears to me that there is a much more naive solution, which proved to be much more efficient as well - good old recursion:
def combos(n,k)
if k == 1
return [n]
end
(1..n-1).flat_map do |i|
combos(n-i,k-1).map { |r| [i, *r].sort }
end.uniq
end
This solution simply reduces the problem each level by taking decreasing the target sum by each number between 1 and the previous target sum, while reducing k by one. Now make sure you don't have duplicates (by sort and uniq) - and you have your answer...
This is great for k < 5, and is much faster than Cary's solution, but as k gets larger, I found that it makes much too many iterations, sort and uniq took a very big toll on the calculation.
So I made sure that won't be needed, by making sure I get only sorted answers - each recursion should check only numbers larger than those already used:
def combos(n,k,min = 1)
if n < k || n < min
return []
end
if k == 1
return [n]
end
(min..n-1).flat_map do |i|
combos(n-i,k-1, i).map { |r| [i, *r] }
end
end
This solution is on par with Cary's on combos(100, 7):
user system total real
My Solution 2.570000 0.010000 2.580000 ( 2.695615)
Cary's 2.590000 0.000000 2.590000 ( 2.609374)
But we can do better: caching! This recursion does many calculations again and again, so caching stuff we already did will save us a lot of work when dealing with long sums:
def combos(n,k,min = 1, cache = {})
if n < k || n < min
return []
end
cache[[n,k,min]] ||= begin
if k == 1
return [n]
end
(min..n-1).flat_map do |i|
combos(n-i,k-1, i, cache).map { |r| [i, *r] }
end
end
end
This solution is mighty fast and passes Cary's solution for large n by light-years:
Benchmark.bm do |bm|
bm.report('Uri') { combos(1000, 3) }
bm.report('Cary') { combos_cary(1000, 3) }
end
user system total real
Uri 0.200000 0.000000 0.200000 ( 0.214080)
Cary 7.210000 0.000000 7.210000 ( 7.220085)
And is on par with k as high as 9, and I believe it is still less complicated than his solution.
You want the number of integer partitions of n into exactly k summands. There is a (computationally) somewhat ugly recurrence for that number.
The idea is this: let P(n,k) be the number of ways to partition n into k nonzero summands; then P(n,k) = P(n-1,k-1) + P(n-k,k). Proof: every partition either contains a 1 or it doesn't contain a 1 as one of the summands. The first case P(n-1,k-1) calculates the number of cases where there is a 1 in the sum; take that 1 away from the sum and partition the remaining n-1 into the now available k-1 summands. The second case P(n-k,k) considers the case where every summand is strictly greater than 1; to do that, reduce all of the k summands by 1 and recurse from there. Obviously, P(n,1) = 1 for all n > 0.
Here's a link that mentions that probably, no closed form is known for general k.

Resources