I'm looking to create a method for Enumerable
that does map
and inject
at the same time. For example, calling it map_with_accumulator
,
[1,2,3,4].map_with_accumulator(:+)
# => [1, 3, 6, 10]
or for strings
['a','b','c','d'].map_with_accumulator {|acc,el| acc + '_' + el}
# => ['a','a_b','a_b_c','a_b_c_d']
I fail to get a solution working. I think I can do it with reduce
. I was working down the path of something like:
arr.reduce([]) {|acc,e| ..... }
with the initial value being an empty array, but I couldn't get it correct.
edit: See Jörg's answer below for a proper solution. Another (somewhat gross) way to do it I realized after reading his answer is by using instance_eval
, which changes the context of the given block to that of the object executing it. So self
is set to reference the array rather than the calling context (which means it is no longer a closure!) and inject
and shift
are called by the array. Convoluted, unnecessarily terse, and confusing to read, but it taught me something new.
['a','b','c','d'].instance_eval do
inject([shift]) {|acc,el| acc << acc.last+el}
end
#=> ['a','ab','abc','abcd']
This operation is called scan or prefix_sum, but unfortunately, there is no implementation in the Ruby core library or standard libraries.
However, your intuition is correct: you can implement it using
Enumerable#inject
. (Actually,Enumerable#inject
is general, every iteration operation can be implemented usinginject
!)Ideally, the behavior should match that of
inject
with its 4 overloads (in which case it would give you the results you specified), but unfortunately, implementing those overloads in Ruby, without privileged access to the VM internals (in particular, the arguments at the send site) is a major pain in the rear section.It goes something like this:
As you can see, the implementation in terms of
inject
itself is rather elegant, the ugliness is solely due to implementing overloading in a language without overloading.