Reputation: 51
I feel this is a super simple query but I'm having a real tough time with immutable nums in my arrays.
I'd like to have a super simple method, which increments numbers in an array by distributing them from the max value.
eg [1,3,5,1] becomes [1,3,0,1] and then iterates upwards and back through to create [2,4,1,3]
what I currently have is the following
arr = [1,3,5,1]
with a method of
def increment_from_max_value(arr)
max = arr.max
max_index = arr.index(max)
arr[max_index] = 0
while max >= 0
arr[max_index..-1].each do |element|
element = element += 1
max = max -= 1
end
end
end
Currently the array isn't even updating and just returns the original values. Which I believe is due to the immutability of FixNums in Ruby, but with a method like map!
, which is able to modify those values, I can't get it to loop back through from a certain starting point like each
will.
Many thanks in advance
Upvotes: 2
Views: 1881
Reputation: 110755
The value of the block variable element
in arr[max_index..-1].each do |element|
is changed by element = element += 1
but that has no effect on arr
.
You could achieve your objective as follows.
def increment_from_max_value(arr)
mx, mx_idx = arr.each_with_index.max_by(&:first)
sz = arr.size
arr[mx_idx] = 0
arr.rotate!(mx_idx + 1).map!.with_index do |e,i|
begin_nbr = mx - i
e += (begin_nbr <= 0 ? 0 : 1 + ((begin_nbr - 1)/sz))
end.rotate!(-mx_idx - 1)
end
arr = [1,3,5,1]
increment_from_max_value(arr)
arr
#=> [2, 4, 1, 3]
arr = [1,2,3,2,1]
increment_from_max_value(arr)
arr
#=> [2, 2, 0, 3, 2]
After computing the maximum value of arr
, mx
, and its index, mx_idx
, and setting arr[mx_idx]
to zero, I rotate the array (counter-clockwise) by mx_idx + 1
, making the position of mx
last. That way the "allocations" of mx
begin with the first element of the rotated array. After performing the allocations I then rotate the array clockwise by the same mx_idx + 1
.
begin_nbr
equals mx
minus the number of indices that precede i
; in effect, the portion of mx
that remains "unallocated" at index i
in the first round of allocations.
I can best explain how this works by salting the method with puts
statements.
def increment_from_max_value(arr)
mx, mx_idx = arr.each_with_index.max_by(&:first)
sz = arr.size
puts "mx = #{mx}, mx_idx = #{mx_idx}, sz = #{sz}"
arr[mx_idx] = 0
arr.rotate!(mx_idx + 1).
tap { |a| puts "arr rotated to make mx position last = #{a}" }.
map!.with_index do |e,i|
begin_nbr = mx - i
puts "e before = #{e}, i = #{i}, begin_nbr = #{begin_nbr}"
e += (begin_nbr <= 0 ? 0 : 1 + ((begin_nbr - 1)/sz))
e.tap { |f| puts "e after change = #{f}" }
end.
tap { |a| puts "arr after changes = #{a}" }.
rotate!(-mx_idx - 1).
tap { |a| puts "arr after rotating back = #{a}" }
end
arr = [1,3,5,1]
increment_from_max_value(arr)
mx = 5, mx_idx = 2, sz = 4
arr rotated to make mx position last = [1, 1, 3, 0]
e before = 1, i = 0, begin_nbr = 5
e after change = 3
e before = 1, i = 1, begin_nbr = 4
e after change = 2
e before = 3, i = 2, begin_nbr = 3
e after change = 4
e before = 0, i = 3, begin_nbr = 2
e after change = 1
arr after changes = [3, 2, 4, 1]
arr after rotating back = [2, 4, 1, 3]
#=> [2, 4, 1, 3]
Upvotes: 0
Reputation: 114248
I'd use divmod
to calculate the increase for each element and the leftover.
For a max value of 5 and array size of 4 you'd get:
5.divmod(4) #=> [1, 1]
i.e. each element has to incremented by 1 (first value) and 1 element (second value) has to be incremented by another 1.
Another example for a max value of 23 and 4 elements:
[1, 3, 23, 1]
23.divmod(4) #=> [5, 3]
each element has to be incremented by 5 and 3 elements have to be incremented by another 1:
[ 1, 3, 23, 1]
# +5 +5 +5 +5
# +1 +1 +1
# = [ 7, 9, 5, 7]
Applied to your method:
def increment_from_max_value(arr)
max = arr.max
max_index = arr.index(max)
arr[max_index] = 0
q, r = max.divmod(arr.size)
arr.each_index { |j| arr[j] += q }
r.times { |j| arr[(max_index + j + 1) % arr.size] += 1 }
end
arr.each_index { |j| arr[j] += q }
simply adds q
to each element.
r.times { |j| arr[(max_index + j + 1) % arr.size] += 1 }
is a little more complicated. It distributes the remainder, starting from 1 after max_index
. The modulo operation ensures that the index will wrap around:
0 % 4 #=> 0
1 % 4 #=> 1
2 % 4 #=> 2
3 % 4 #=> 3
4 % 4 #=> 0
5 % 4 #=> 1
6 % 4 #=> 2
# ...
Upvotes: 1
Reputation: 11193
I think there is something wrong with the while
loop. I did not investigate but you can see that this line arr[max_index] = 0
mutates the array.
I don't know if I've understood the logic, but this should return the desired output:
def increment_from_max_value(arr)
max = arr.max
arr[arr.index(max)] = 0
indexes = (0...arr.size).to_a.reverse
max.times do
arr[indexes.first] += 1
indexes.rotate!
end
end
Upvotes: 0