Array.inject

January 23, 2011

Having had a lot of fun last week doing Uncle Bob’s prime factors kata, I’ve been doing the Codebreaker example from the RSpec book this week. In section 9.2 the number_match_count method is refactored to be

def number_match_count
  total_match_count - exact_match_count
end

And the problem of implementing total_match_count is discussed. The book starts with

def total_match_count
  count = 0
  secret = @secret.split('')
  @guess.split('').map do |n|
    if secret.include?(n)
      secret.delete_at(secret.index(n))
      count += 1
    end
  end
  count
end

I didn’t like this so in a separate branch I came up with

 def total_match_count
   total_match_count = 0
   secret = @secret.clone # need a copy as we will slice destructively
   @guess.each_char do |char|
     pos = secret.index(char)
     secret.slice!(pos) and total_match_count +=1 if pos
   end
   total_match_count
 end

Which is OK, but fairly similar.

What the RSpec book is leading upto though is

def total_match_count
  secret = @secret.split('')
  @guess.split('').inject(0) do |count, n|
    count + (delete_first(secret, n) ? 1 : 0)
  end
end

def
  delete_first(code, n) code.delete_at(code.index(n)) if code.index(n)
end

This uses Array.inject, something I have always avoided. This is the second time the book has focused on Array.inject, and I started thinking why the fascination with inject. What is it that Ruby programmers like about it that I’m missing.

Some googling produced

  1. Jay Fields loves inject

  2. This thread, of which the best bits are:

    -

     How I remember:  Inject takes a binary operation (e.g. +) and injects
     it between each element of a list.
    
        [1,2,3].inject { |a,b| a+b }  => 1+2+3
    
     -- Jim Weirich
    

    -

     ... there is a fundamental difference between the inject and each
     versions.  The "each" verison is procedural.  It defines state (the
     value of b) and how that state changes in each iteration (b += a).
    
     The "inject" version is functional.  There are no extra state
     variables in our code that need initialization.  And the expression
     itself is the value, rather than having a side effect on a local
     variable.
    
     Not that one way is better than the other, just noting differences.
    
     -- Jim Weirich
    

-

    So, in math-speak, I think one could say:

    inject applies a block pairwise as an n-ary operation over an
    Enumerable, with an optional initial value.

    -- A LeDonne

Perhaps Array.inject is worth investigating further as a step towards doing more functional programming.