5

I'm essentially going through an exercise for rewriting a basic form of the inject method from the Enumerable module and my solution was not doing anything as I was using #first:

def injecting(*acc, &block)
  acc = acc.empty? ? self.first : acc.first
  self.each do |x|
     acc = block.call(acc, x)
  end  
  acc
end

I then came across another solution which was using #shift instead of #first and was working just fine:

def injecting(*acc, &block)
  acc = acc.empty? ? self.shift : acc.first
  self.each do |x|
    acc = block.call(acc, x)
  end  
  acc
end  

I know that #first returns the first element of the array but doesn't alter it while #shift returns and alters it, but I'm having a hard time understanding how the below code would still get the desired result if you were to mutate the array by dropping the first element:

[1,2,3].injecting { |a,x| a + x } # --> 6

Any words of wisdom would be greatly appreciated..

Thanks!

Maikon
  • 1,382
  • 16
  • 16
  • 2
    Note that with both you could replace `def injecting(*acc, &block)` with `def injecting(*acc)` and `acc = block.call(acc, x)` with `acc = yield(acc, x)`. Even though you have stated these are from the Enumerable module, you may want to consider wrapping the first in `module Enumerable` / `end`. Good question, well-stated. – Cary Swoveland Feb 01 '14 at 18:40
  • Cary that's a lifesaver! I'm in the process of learning blocks in general and I prefer using the yield keyword when possible. I could not incorporate it here, clearly because I was doing something wrong or I don't fully understand it yet but I'll fix that too with time ;) Thanks! – Maikon Feb 01 '14 at 18:50
  • 1
    I highly recommend [this explanation](http://innig.net/software/ruby/closures-in-ruby) of blocks, procs and more. – Cary Swoveland Feb 01 '14 at 19:40
  • Just from the sheer number of examples, this looks very good. My night is booked. Thanks Cary! – Maikon Feb 01 '14 at 20:01

1 Answers1

4

Just add one line p acc to debug it.

(arup~>~)$ pry --simple-prompt
>> module Enumerable
 |   def injecting(*acc, &block)
 |     acc = acc.empty? ? self.shift : acc.first  
 |     p acc  
 |     self.each do |x|  
 |       acc = block.call(acc, x)    
 |     end      
 |     acc  
 |   end    
 | end  
=> nil
>> [1,2,3].injecting { |a,x| a + x } 
1
=> 6
>> 

When you called the method injecting on the array [1,2,3], as acc was empty, sef.shift gets invoked. Now acc is 1. Now self has only [2,3]. so the call self.each.. first pass 2, and then block gets invoked and the result of a = x is assigned to acc, whis now holds 3. So now on the next iteration, from self next value passed(3), and block gets called again, and perform a + x again, so now value of acc is 6. So you got the result 6.

A one level more debugging :

(arup~>~)$ pry --simple-prompt
>> module Enumerable
 |   def injecting(*acc, &block)
 |     acc = acc.empty? ? self.shift : acc.first  
 |     p "acc now holds #{acc}"  
 |     p "elements in self is #{self}"  
 |     self.each do |x|  
 |       acc = block.call(acc, x)    
 |       p "current value of acc is #{acc}"    
 |     end      
 |     acc  
 |   end  
 | end  
=> nil
>> [1,2,3].injecting { |a,x| a + x } 
"acc now holds 1"
"elements in self is [2, 3]"
"current value of acc is 3"
"current value of acc is 6"
=> 6
>>

As per OP's comment

Am I safe assuming that by calling first, is still returning the 1st item but starts the iteration from that item, hence why I would get 7?

Again I would suggest you to add some debugging messages, to see what's going go under-hood. Look below :

(arup~>~)$ pry --simple-prompt
>> module Enumerable
 |   def injecting(*acc, &block)
 |     acc = acc.empty? ? self.first : acc.first  
 |     p "acc now holds #{acc}"  
 |     p "elements in self is #{self}"  
 |     self.each do |x|  
 |       acc = block.call(acc, x)    
 |       p "current value of acc is #{acc}"    
 |     end      
 |     acc  
 |   end  
 | end  
=> nil
>> [1,2,3].injecting { |a,x| a + x }
"acc now holds 1"
"elements in self is [1, 2, 3]"
"current value of acc is 2"
"current value of acc is 4"
"current value of acc is 7"
=> 7
>> 

self.first just returns the first element, which is 1, and get assigned to acc. But self didn't get modified. But in case of self.shift, that 1 was assigned to acc and at the sane time got removed from self, and then self has [2,3].

Now in this part, self.each.. code passes 3 values of self, which are 1,2 and 3. Now the summation is 6, and added with 1, which is the first acc value, when self.first got called. This is how the final result of acc is 7.

Community
  • 1
  • 1
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
  • Thanks Arup! Using the debugging messages does make it a lot clearer. Am I safe assuming that by calling first, is still returning the 1st item but starts the iteration from that item, hence why I would get 7? – Maikon Feb 01 '14 at 18:24
  • 1
    @Maikon Now I hope it is clear!! why one code outputs `6` and the other is `7`. – Arup Rakshit Feb 01 '14 at 18:35
  • Adding useful debugging messages is another tool I'm trying to add to my toolbox. Your answer has cleared up a lot of things. Thanks again! – Maikon Feb 01 '14 at 18:38
  • 1
    Excellent answer, Arup. Good to see you're using Pry. – Cary Swoveland Feb 01 '14 at 18:42
  • @CarySwoveland You told.. and I wouldn't use.. It wouldn't ever be happened.. :) – Arup Rakshit Feb 01 '14 at 18:48
  • Not quite `inject`: `[1,2,3].inject(:+) => 6`, `[1,2,3].injecting(&:+) => 6`, `[1,2,3].injecting(:+) => NoMethodError: undefined method `call' for nil:NilClass`. – Cary Swoveland Feb 01 '14 at 18:49
  • @CarySwoveland I just tried to re implement it as per OP's one.. Thus, it is will perform only addition, where as original `#inject` does a lot's of stuff.. – Arup Rakshit Feb 01 '14 at 18:51
  • 1
    `[2,3,4].injecting(&:*) => 24`. – Cary Swoveland Feb 01 '14 at 18:53
  • 1
    I actually installed and started using Pry today. I assume from your responses, I made the right choice :). – Maikon Feb 01 '14 at 18:53
  • @CarySwoveland humm!! that's cool.. But I don't know how to make it working `[1,2,3].injecting(:+)` or `[2,3,4].injecting(:*)`.. :( – Arup Rakshit Feb 01 '14 at 18:54
  • @ArupRakshit I like the updated version without the if statement, but does the 0 provide any limitations for the future? If I'm dealing with other types of data? – Maikon Feb 01 '14 at 18:55
  • @Maikon I removed it, because that was not the actual `#inject` implementation.. a lot's of thing then I need to consider.. because `#inject` does a lots of operation.. – Arup Rakshit Feb 01 '14 at 18:57
  • @CarySwoveland: Depends on the version. `Enumerable#inject` originally didn't take a symbol either. The original version in 1.8.6 only took an initial element like this method. (I think I'm showing my age…) – Chuck Feb 01 '14 at 18:59
  • Thanks, @Chuck. Arup, I just hit "start" on my stopwatch. Get to work on it. – Cary Swoveland Feb 01 '14 at 19:01
  • @ArupRakshit I've been working on it since yesterday and I think I failed in the same 0 trap, as a friend pointed out it wouldn't work with other operations but you're original version still works with something like this: `['hi','hello','ho'].injecting { |a,x| a.length > x.length ? a : x } --> 'hello'` – Maikon Feb 01 '14 at 19:06