-1
def compute(ary)
  return nil unless ary
  ary.map { |a, b| !b.nil? ? a + b : a }
end

compute([1,2],[3,4])

Can someone please explain to me how compute adds the inner array's values?

To me it seems that calling map on that array of arrays would add the two arrays together, not the inner elements of each array.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Bo G.
  • 107
  • 2
  • 9
  • 1
    I tried this and got "in `compute': wrong number of arguments (2 for 1) (ArgumentError)" – Some Guy Jul 18 '14 at 15:33
  • I copied this code line for line from RubyMonk, and I've noticed that the code he uses is kind of funky. I never tried this in IRB, so thank you for the heads up. – Bo G. Jul 18 '14 at 19:15

2 Answers2

3

map basically iterates over the elements of the object:

foo = [
  ['a', 'b'],
  ['c', 'd']
]

foo.map{ |ary| puts ary.join(',') }
# >> a,b
# >> c,d

In this example it's passing each sub-array, which is assigned to ary.

Looking at it a bit differently:

foo.map{ |ary| puts "ary is a #{ary.class}" }
# >> ary is a Array
# >> ary is a Array

Because Ruby lets us assign multiple values at once, that could have been written:

foo.map{ |item1, item2| puts "item1: #{ item1 }, item2: #{ item2 }" }
# >> item1: a, item2: b
# >> item1: c, item2: d

If map is iterating over an array of hashes, each iteration yields a sub-hash to the block:

foo = [
  {'a' => 1},
  {'b' => 2}
]

foo.map{ |elem| puts "elem is a #{ elem.class }" }
# >> elem is a Hash
# >> elem is a Hash

If map is iterating over a hash, each iteration yields the key/value pair to the block:

foo = {
  'a' => 1,
  'b' => 2
}

foo.map{ |k, v| puts "k: #{k}, v: #{v}" }
# >> k: a, v: 1
# >> k: b, v: 2

However, if you only give the block a single parameter, Ruby will assign both the key and value to the variable so you'll see it as an array:

foo.map{ |ary| puts "ary is a #{ary.class}" }
# >> ary is a Array
# >> ary is a Array

So, you have to be aware of multiple things that are happening as you iterate over the container, and as Ruby passes the values into map's block.

Beyond all that, it's important to remember that map is going to return a value, or values, for each thing passed in. map, AKA collect, is used to transform the values. It shouldn't be used as a replacement for each, which only iterates. In all the examples above I didn't really show map used correctly because I was trying to show what happens to the elements passed in. Typically we'd do something like:

foo = [['a', 'b'], ['c', 'd']]
foo.map{ |ary| ary.join(',') }
# => ["a,b", "c,d"]

Or:

bar = [[1,2], [3,4]]
bar.collect{ |i, j| i * j }
# => [2, 12]

There's also map! which changes the object being iterated, rather than returns the values. I'd recommend avoiding map! until you're well aware of why it'd be useful to you, because it seems to confuse people no end unless they understand how variables are passed and how Arrays and Hashes work.

The best thing is to play with map in IRB. You'll be able to see what's happening more easily.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • I am just baffled at the mechanism behind the block when you use two variables in the pipes instead of one. You use one variable, and ruby knows that you're referring to each array within the array. You use two, and Ruby knows that you're referring to each individual element within each individual array within the array. Thank you, though. Your example was very helpful. – Bo G. Jul 18 '14 at 19:42
  • @BoG.: Argument binding for blocks and `Proc`s uses different semantics than argument binding for methods and lambdas. Argument binding for blocks and `Proc`s is much closer to assignment semantics than method/lambda argument semantics. (In fact, up to Ruby 1.8, blocks/`Proc`s actually *used* assignment semantics.) See http://stackoverflow.com/a/19841196/2988, http://stackoverflow.com/a/2270433/2988, and http://stackoverflow.com/a/16073076/2988 for some examples. And think about `Hash#each`, `Hash#map` and friends and how awkward they would be to use if this weren't the case. – Jörg W Mittag Jul 19 '14 at 01:01
0

I think I figured this out myself.

map selects the first array of the array-of-arrays and pipes it into the block. The variables a and b therefore refer to the first array's inner elements, rather than the first array and the second array in the array-of-arrays.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Bo G.
  • 107
  • 2
  • 9
  • No, `map` doesn't select anything. As Some Guy commented, your code will raise an error, and will not run in the first place. – sawa Jul 18 '14 at 15:36