How is the reciprocal sum in Ruby?
I have no idea how to call this in correct mathematical terms. Consider a method that takes two digits:
def num_of_sum(total, group_count)
end
where total
is an integer and group_count
is an integer.
How do I get a "nicely" grouped array of group_count
-length integers that sum up to total
.
My spec would look like this:
describe "number to sum of" do
it "grabs all numbers" do
expect(num_of_sum(10, 2)).to eq([5,5])
expect(num_of_sum(10, 3)).to eq([3,3,4])
expect(num_of_sum(20, 3)).to eq([6,7,7])
expect(num_of_sum(100, 3)).to eq([33,33,34])
expect(num_of_sum(100, 2)).to eq([50,50])
end
end
I tried this, which works:
def num_of_sum(total, in_groups_of)
result = []
section_count ||= (total.to_f / in_groups_of.to_f).round
while(total > 0)
total -= section_count
if (total - section_count) < 0 && (total + section_count).even?
section_count += total
total -= total
end
result << section_count
end
result
end
But, for example, this specification doesn't work:
expect(num_of_sum(67,5)).to eq([13,13,13,14,14])
I need an array to contain numbers that are as close to each other as possible. But the array is limited in length group_count
.
Does anyone know what the mathematical name is for this so that I can search more precisely?
source to share
def n_parts(num, groupcount)
div, mod = num.divmod(groupcount)
Array.new(groupcount-mod, div) + Array.new(mod, div+1)
end
n_parts(100,3) => [33, 33, 34]
Documents Array.new and Fixnum.divmod
source to share
The mathematical term for this is an integer partition
A more direct approach to this is to notice that if you do an integer division (round down) of the total by the number of groups, then your sum will be short over the total mod_of_groups, so you just need to distribute that sum across an array:
def even_partition(total, number_of_groups)
quotient, remainder = total.divmod(number_of_groups)
(number_of_groups-remainder).times.collect {quotient} +
remainder.times.collect { quotient + 1}
end
source to share
The naive implementation is this:
Let's take an example (20, 3)
. As a result, you want to get three numbers.
20 / 3 # => 6
This is your "base" value. Create an array of three gears [6, 6, 6]
. This will give you 18. Now you need to distribute the remaining 2 as evenly as possible. For example, list the elements of an array and increment each one by one until you have a value to propagate. Result [7, 7, 6]
. Good enough I think.
Possible (working) implementation:
def breakdown(total, group_count)
avg_value, extra = total.divmod(group_count)
result = Array.new(group_count, avg_value)
extra.times do |i|
result[i] += 1
end
result
end
breakdown(10, 2) == [5, 5] # => true
breakdown(10, 3) == [4, 3, 3] # => true
breakdown(20, 3) # => [7, 7, 6]
source to share
I don't know what it's called, but here's the solution:
def num_of_sum sum, count
result = [i = sum / count] * count # prepare an array e.g. [3,3,3] for 10,3
result[sum - i * count..-1] + # these should be left intact
result[0...sum - i * count].map { |i| i + 1 } # these are ++’ed
end
Hope it helps.
source to share
Another way:
def floors_then_ceils(n, groups)
floor, ceils = n.divmod(groups)
groups.times.map { |i| (i < groups-ceils) ? floor : floor + 1 }
end
floors_then_ceils(10, 3)
#=> [3, 3, 4]
floors_then_ceils(9, 3)
#=> [3, 3, 3]
Alternatively groups.times.map...
can be replaced with:
Array.new(groups-ceils, floor).concat(Array.new(ceils, floor+1))
source to share