Reputation: 103
What is the most concise and explicit way to write a method for this?
Given an array
a
of numbers and a numbern
, find then
consecutive elements ofa
whose sum is the largest.Return the largest sum and the index of the first element in the group.
For example, with a = [1, 1, 1, 1, 1, 1, 1, 2]
and n = 2
, the
result would be a sum 3
and position 6
.
Upvotes: 0
Views: 80
Reputation: 110665
arr = [1,3,2,4,3,5,2,1,3,4,2,5,1]
size = 3
Inefficient but pretty
arr.each_cons(size).with_index.map { |a,i| [a.inject(:+), i] }.max_by(&:first)
#=> [12, 3]
Efficient but whupped with an ugly stick1
tot = arr[0,size].inject(:+)
(1..arr.size-size).each_with_object([tot, 0]) do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
Steps performed by the pretty one
enum0 = arr.each_cons(size)
#=> #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:each_cons(3)>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:
# each_cons(3)>:with_index>
Carefully examine the above return value for enum1
. You will see it is effectively a "compound" enumerator. We can see the values that enum1
will generate and pass to map
by converting it to an array:
enum1.to_a
#=> [[[1, 3, 2], 0], [[3, 2, 4], 1], [[2, 4, 3], 2], [[4, 3, 5], 3],
# [[3, 5, 2], 4], [[5, 2, 1], 5], [[2, 1, 3], 6], [[1, 3, 4], 7],
# [[3, 4, 2], 8], [[4, 2, 5], 9], [[2, 5, 1], 10]]
Continuing:
b = enum1.map { |a,i| [a.inject(:+), i] }
#=> [[6, 0], [9, 1], [9, 2], [12, 3], [10, 4], [8, 5],
# [6, 6], [8, 7], [9, 8], [11, 9], [8, 10]]
Note the since the first element of enum1
that map
passes to the block is [[1, 3, 2], 0]
, the two block variables are assigned as follows (using parallel or multiple assignment):
a, i = [[1, 3, 2], 0]
#=> [[1, 3, 2], 0]
a #=> [1, 3, 2]
i #=> 0
and the block calculation is performed:
[a.inject(:+), i]
#=> [6, 0]
Lastly,
b.max_by(&:first)
#=> [12, 3]
Enumerable#max_by determines the largest value among
b.map(&:first)
#=> [6, 9, 9, 12, 10, 8, 6, 8, 9, 11, 8]
Steps performed by the less pretty one
a = arr[0,size]
#=> [1, 3, 2]
tot = a.inject(:+)
#=> 6
enum = (1..arr.size-size).each_with_object([tot, 0])
#=> (1..13-3).each_with_object([6, 0])
#=> #<Enumerator: 1..10:each_with_object([6, 0])>
enum.to_a
#=> [[1, [6, 0]], [2, [6, 0]], [3, [6, 0]], [4, [6, 0]], [5, [6, 0]],
# [6, [6, 0]], [7, [6, 0]], [8, [6, 0]], [9, [6, 0]], [10, [6, 0]]]
enum.each do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
The first element of enum
, [1, [6, 0]]
, is passed to the block, assigned to the block variables and the block calculation is performed:
i, best = [1, [6, 0]]
#=> [1, [6, 0]]
i #=> 1
best
#=> [6, 0]
tot += arr[i+size-1] - arr[i-1]
# tot = 6 + arr[1+3-1] - arr[1-1]
# = 6 + 4 - 1
# = 9
best.replace([tot, i]) if tot > best.first
#=> best.replace([9, 1]) if 9 > 6
#=> [9, 1]
best
#=> [9, 1]
The remaining calculations are similar.
1 Credit to Bo Diddley (at 2:51)
Upvotes: 6