Method: Enumerable#inject
- Defined in:
- enum.c
#inject(symbol) ⇒ Object #inject(initial_value, symbol) ⇒ Object #inject {|memo, value| ... } ⇒ Object #inject(initial_value) {|memo, value| ... } ⇒ Object
Returns the result of applying a reducer to an initial value and the first element of the Enumerable. It then takes the result and applies the function to it and the second element of the collection, and so on. The return value is the result returned by the final call to the function.
You can think of
[ a, b, c, d ].inject(i) { |r, v| fn(r, v) }
as being
fn(fn(fn(fn(i, a), b), c), d)
In a way the inject
function injects the function between the elements of the enumerable.
inject
is aliased as reduce
. You use it when you want to reduce a collection to a single value.
The Calling Sequences
Let’s start with the most verbose:
enum.inject(initial_value) do |result, next_value|
# do something with +result+ and +next_value+
# the value returned by the block becomes the
# value passed in to the next iteration
# as +result+
end
For example:
product = [ 2, 3, 4 ].inject(1) do |result, next_value|
result * next_value
end
product #=> 24
When this runs, the block is first called with 1
(the initial value) and 2
(the first element of the array). The block returns 1*2
, so on the next iteration the block is called with 2
(the previous result) and 3
. The block returns 6
, and is called one last time with 6
and 4
. The result of the block, 24
becomes the value returned by inject
. This code returns the product of the elements in the enumerable.
First Shortcut: Default Initial value
In the case of the previous example, the initial value, 1
, wasn’t really necessary: the calculation of the product of a list of numbers is self-contained.
In these circumstances, you can omit the initial_value
parameter. inject
will then initially call the block with the first element of the collection as the result
parameter and the second element as the next_value
.
[ 2, 3, 4 ].inject do |result, next_value|
result * next_value
end
This shortcut is convenient, but can only be used when the block produces a result which can be passed back to it as a first parameter.
Here’s an example where that’s not the case: it returns a hash where the keys are words and the values are the number of occurrences of that word in the enumerable.
freqs = File.read("README.md")
.scan(/\w{2,}/)
.reduce(Hash.new(0)) do |counts, word|
counts[word] += 1
counts
end
freqs #=> {"Actions"=>4,
"Status"=>5,
"MinGW"=>3,
"https"=>27,
"github"=>10,
"com"=>15, ...
Note that the last line of the block is just the word counts
. This ensures the return value of the block is the result that’s being calculated.
Second Shortcut: a Reducer function
A reducer function is a function that takes a partial result and the next value, returning the next partial result. The block that is given to inject
is a reducer.
You can also write a reducer as a function and pass the name of that function (as a symbol) to inject
. However, for this to work, the function
-
Must be defined on the type of the result value
-
Must accept a single parameter, the next value in the collection, and
-
Must return an updated result which will also implement the function.
Here’s an example that adds elements to a string. The two calls invoke the functions String#concat and String#+ on the result so far, passing it the next value.
s = [ "cat", " ", "dog" ].inject("", :concat)
s #=> "cat dog"
s = [ "cat", " ", "dog" ].inject("The result is:", :+)
s #=> "The result is: cat dog"
Here’s a more complex example when the result object maintains state of a different type to the enumerable elements.
class Turtle
def initialize
@x = @y = 0
end
def move(dir)
case dir
when "n" then @y += 1
when "s" then @y -= 1
when "e" then @x += 1
when "w" then @x -= 1
end
self
end
end
position = "nnneesw".chars.reduce(Turtle.new, :move)
position #=>> #<Turtle:0x00000001052f4698 @y=2, @x=1>
Third Shortcut: Reducer With no Initial Value
If your reducer returns a value that it can accept as a parameter, then you don’t have to pass in an initial value. Here :*
is the name of the times function:
product = [ 2, 3, 4 ].inject(:*)
product # => 24
String concatenation again:
s = [ "cat", " ", "dog" ].inject(:+)
s #=> "cat dog"
And an example that converts a hash to an array of two-element subarrays.
nested = {foo: 0, bar: 1}.inject([], :push)
nested # => [[:foo, 0], [:bar, 1]]
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 |
# File 'enum.c', line 1040 static VALUE enum_inject(int argc, VALUE *argv, VALUE obj) { struct MEMO *memo; VALUE init, op; rb_block_call_func *iter = inject_i; ID id; int num_args; if (rb_block_given_p()) { num_args = rb_scan_args(argc, argv, "02", &init, &op); } else { num_args = rb_scan_args(argc, argv, "11", &init, &op); } switch (num_args) { case 0: init = Qundef; break; case 1: if (rb_block_given_p()) { break; } id = rb_check_id(&init); op = id ? ID2SYM(id) : init; init = Qundef; iter = inject_op_i; break; case 2: if (rb_block_given_p()) { rb_warning("given block not used"); } id = rb_check_id(&op); if (id) op = ID2SYM(id); iter = inject_op_i; break; } if (iter == inject_op_i && SYMBOL_P(op) && RB_TYPE_P(obj, T_ARRAY) && rb_method_basic_definition_p(CLASS_OF(obj), id_each)) { return ary_inject_op(obj, init, op); } memo = MEMO_NEW(init, Qnil, op); rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo); if (UNDEF_P(memo->v1)) return Qnil; return memo->v1; } |