boulder_ruby boulder_ruby - 4 months ago 8
Ruby Question

Is it possible to separately declare block parameters of an array element which is passed into a block for the inject method?

Here is what I'm doing right now that currently works, but I feel is a bit lacking in syntactic sugar...

f = RGeo::Geos.factory
coords = [ [1,1], [2,2], [1,3] ]
points = coords.inject([]) {|points, coord| points << f.point(coord[0], coord[1]); points }
#=> [#<RGeo::Geos::CAPIPointImpl:0x3fff0950a508 "POINT (1.0 1.0)">, #<RGeo::Geos::CAPIPointImpl:0x3fff0950a47c "POINT (2.0 2.0)">, #<RGeo::Geos::CAPIPointImpl:0x3fff0950a454 "POINT (1.0 3.0)">]


Here is what I would like to do:

points = coords.inject([]) {|points, x, y| points << f.point(x,y); points }
#=> [nil, nil, nil]


Right now that is returning an array of three nil values. Is it possible to declare the array values of an element being passed into the inject block separately like it is possible with the each method?

Side note:
It's safe to use same variable name for the first inject block parameter (the "result" variable) and the variable on the left side of the equals sign, right? I'm thinking yes because this would just be a typical case of recursion. Please comment if you feel or know otherwise.

Answer

This will do the trick:

points = coords.inject([]) {|points, (x, y)| points << f.point(x,y); points }

This works generally in any block; if one of the block parameters is an array of n elements, you can capture those elements using n parenthesized parameter names. If you use more than n parameter names in parentheses, the extras will be nil:

[[1,2,3],[4,5]].map {|(a,b,c)| "#{a}+#{b}+#{c.inspect}" } #=> ["1+2+3", "4+5+nil"]

If you use too few, parameter names, the remainder will be discarded:

[[1,2,3],[4,5]].map {|(a,b)| "#{a}+#{b}" } #=> ["1+2", "4+5"]

You can use the splat operator to capture, the remainder instead, just like with regular arguments:

[[1,2,3],[4,5]].map {|(a,*b)| "#{a}+#{b}" } #=> ["1+[2, 3]", "4+[5]"]

You can even nest:

[[1,2,[3,4]],[5,6,[7,8]]].map {|(a,b,(c,d))| a+b+c+d } #=> [10, 26]

Mind, nesting this can get confusing pretty quickly, so use it sparingly.

Also note that those outer parenthesis are not, strictly, necessary in the above examples, but they change the behavior when only one argument is being taken from the array:

# Makes no difference
[[1,2,3],[4,5,6]].map {| a,b,c | "#{a}+#{b}+#{c}" } #=> ["1+2+3", "4+5+6"]
[[1,2,3],[4,5,6]].map {|(a,b,c)| "#{a}+#{b}+#{c}" } #=> ["1+2+3", "4+5+6"]

# Makes a big difference
[[1,2,3],[4,5,6]].map {| a | a } #=> [[1, 2, 3], [4, 5, 6]]
[[1,2,3],[4,5,6]].map {|(a)| a } #=> [1, 4]
Comments