Array.inject
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
-
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.