I am a a bit confused by some of the behavior of the of the inject (alias reduce) command in Ruby
For any array, the second element (referenced after the accumulator) reflects the last element in the array so
[1,2,10].inject{|accumulator,y| y}
gives 10
whereas the first element is the initial value of the array (that subsequently gets the result of any specified operation) if not otherwise specified so
[1,2,10].inject{|accumulator,y| accumulator}
gives 1
This results in the expected behavior for
[1,2,10].inject{|accumulator,y| accumulator + y}
gives 13
OR
[1,2,10].inject{|accumulator,y| y + accumulator}
gives 13
However,
If a string is used
["b","i","g"].inject{|x,y| y + x}
the result is "gib".
Why is it not "gbi"?
I would expect (last element + first element)+ i OR ("g"+"b")+ "i"
It also works as expected for subtraction when x is the value to be acted on first
[1,2,10].inject{|x,y| x - y}
gives -11 i.e (1-2) - 10 = -11 as expected
However,
when 'y' is the first element to be acted on
[1,2,10].inject{|x,y| y - x}
gives 9.
This is surprising. Only the last element and the first are used i.e 10-1.
Why is there is no accumulated result?!
For any array, the second element (referenced after the accumulator) reflects the last element in the array
This incorrect statement is the source of the misunderstanding.
Look at the following code:
["b", "i", "g", "g", "e", "s", "t"].inject do |x, y|
puts({ y: y, x: x, "y + x": (y + x) })
y + x
end
which prints
{y: "i", x: "b", "y + x": "ib"}
{y: "g", x: "ib", "y + x": "gib"}
{y: "g", x: "gib", "y + x": "ggib"}
{y: "e", x: "ggib", "y + x": "eggib"}
{y: "s", x: "eggib", "y + x": "seggib"}
{y: "t", x: "seggib", "y + x": "tseggib"}
As you can see, the second block parameter isn't the last element in the array, it's the next element in the array. ["b", "i", "g"].inject
uses "b"
as the first accumulator and passes "i"
, then "g"
.
Only the last element and the first are used i.e 10-1. Why is there is no accumulated result?!
This statement exhibits the same misunderstanding as above. The return value is 9
not because it's "last minus first" (10 - 1
) but because it's "last minus the result of second minus first" (10 - (2 - 1)
). The resulting values just happen to be the same in your example.
Here's an illustration of how it actually works, using a different array:
[1, 4, 9, 16, 25].inject do |x, y|
puts({ y: y, x: x, "y - x": (y - x) })
y - x
end
This prints
{y: 4, x: 1, "y - x": 3}
{y: 9, x: 3, "y - x": 6}
{y: 16, x: 6, "y - x": 10}
{y: 25, x: 10, "y - x": 15}
At each step after the first, the value of x
is set to the result of y - x
in the previous step. So, there is, in fact, an accumulated result, regardless of the contents of the array and regardless of which operation is used.