How does Array.map work for 2D arrays?


#1

I was reading this example:

pt = [ [ “Check”, “check” ], [ “Credit card”, “cc” ], [ “Purchase
order”, “po” ] ]
pt.map {|value| value}

Displays the 2D Array elements as expected => [[“Check”, “check”],
[“Credit card”, “cc”], [“Purchase order”, “po”]]

but a two-parameter block suppled to map like below:

pt.map {|disp, value| value}
=> [“check”, “cc”, “po”]

is able to give me the second element of the 2D array.

i looked in the ruby doc and on the net for examples, but 2D arrays are
typically not covered.

while, i love this functionality, my question is -

How does this 2 parameter variant really work?

How does the array.map “figure out” and pass disp to param1 and value to
param2?


#2

Karthik N. wrote:

… a two-parameter block suppled to map like below:

pt.map {|disp, value| value}
=> [“check”, “cc”, “po”]

is able to give me the second element of the 2D array.

while, i love this functionality, my question is -

How does this 2 parameter variant really work?

How does the array.map “figure out” and pass disp to param1 and value to
param2?

Two points. First, I don’t know how it figures it out. Second, your
question can be “answered” by looking at another example…

a = [ [ ‘billy’, ‘goat’, ‘cheese’ ], [ ‘silly’, ‘hats’ ] ]

a.map { |x, y, z| z } # => [“cheese”, nil]

Observation tells us that map isn’t really figuring out that you have a
2D array, it’s simply assuming that if you’re asking for two arguments
(or three or one-hundred), that your array has them.

Make sense?


#3

yes makes sense!!

here’s some experiments i did, and it’s more of a ruby thing than a map
thing!

irb(main):001:0> a = 1, 2
=> [1, 2]

irb(main):002:0> def map(a) yield a end
=> nil

irb(main):003:0> map(a) { | x, y | p y }
2
=> nil

irb(main):004:0> map(a) {| x | p x }
[1, 2]
=> nil

Two points. First, I don’t know how it figures it out. Second, your
question can be “answered” by looking at another example…

a = [ [ ‘billy’, ‘goat’, ‘cheese’ ], [ ‘silly’, ‘hats’ ] ]

a.map { |x, y, z| z } # => [“cheese”, nil]

Observation tells us that map isn’t really figuring out that you have a
2D array, it’s simply assuming that if you’re asking for two arguments
(or three or one-hundred), that your array has them.

Make sense?


#4

wow, robert, that makes a lot of sense Thanks !

now my next question: how would a newbie (like me) know by looking at
the ruby doc that Array.map could actually support 2D arrays in this
fashion?

(or is it that it just becomes “obvious” as one uses Ruby more and
more?!)


#5

On 14.01.2007 11:21, Karthik N. wrote:

How does this 2 parameter variant really work?

How does the array.map “figure out” and pass disp to param1 and value to
param2?

It doesn’t really have to do anything at all:

irb(main):001:0> def foo() yield 1,2 end
=> nil
irb(main):002:0> foo {|a| p a}
(irb):2: warning: multiple values for a block parameter (2 for 1)
from (irb):1
[1, 2]
=> nil
irb(main):003:0> foo {|*a| p a}
[1, 2]
=> nil
irb(main):004:0> foo {|a,b| p a}
1
=> nil
irb(main):005:0> def foo() yield [1,2] end
=> nil
irb(main):006:0> foo {|a| p a}
[1, 2]
=> nil
irb(main):007:0> foo {|*a| p a}
[[1, 2]]
=> nil
irb(main):008:0> foo {|a,b| p a}
1
=> nil

You see in line 6 and 8 how it works.

However, map could also evaluate the block’s arity:

irb(main):009:0> def foo(&b) p b.arity; b[1,2] end
=> nil
irb(main):010:0> foo {|a| p a}
1
(irb):10: warning: multiple values for a block parameter (2 for 1)
from (irb):9
[1, 2]
=> nil
irb(main):011:0> foo {|a,b| p a}
2
1
=> nil
irb(main):012:0> foo {|*a| p a}
-1
[1, 2]
=> nil
irb(main):013:0> def foo(&b) p b.arity; b[[1,2]] end
=> nil
irb(main):014:0> foo {|a| p a}
1
[1, 2]
=> nil
irb(main):015:0> foo {|a,b| p a}
2
1
=> nil
irb(main):016:0> foo {|*a| p a}
-1
[[1, 2]]
=> nil

HTH

robert


#6

On 1/14/07, Robert K. removed_email_address@domain.invalid wrote:

So, an Array is automatically assigned element wise you do not need to
pattern matching:
=> [1, [2, 3]]
irb(main):001:0> h={}
This is of course a silly example. I chose these values in order to
make checking what’s going on easier (i.e. summing all keys and values
equals h.size * 10).

Kind regards

   robert

Just wanted to throw in another set of thanks(late, I know); that
response
very clearly answered a question I had regarding map.


#7

On 14.01.2007 12:49, karthik wrote:

wow, robert, that makes a lot of sense Thanks !

You’re welcome!

now my next question: how would a newbie (like me) know by looking at
the ruby doc that Array.map could actually support 2D arrays in this
fashion?

(or is it that it just becomes “obvious” as one uses Ruby more and
more?!)

I guess the latter. What you see is basically the effect of a
combination of features: You’re actually not dealing with a 2D Array but
with an Array that contains Arrays, or more generally, an Enumerable
that contains Enumerables. A true 2D Array would make sure that every
row has the same number of elements and similarly for columns, that’s
why it’s not a 2D Array. The other aspect is how yield and assignments
work. There is some automatic, err, how would you call that, unrolling?

irb(main):001:0> a,b=1,2
=> [1, 2]
irb(main):002:0> a
=> 1
irb(main):003:0> b
=> 2
irb(main):004:0> a,b=[1,2]
=> [1, 2]
irb(main):005:0> a
=> 1
irb(main):006:0> b
=> 2

So, an Array is automatically assigned element wise you do not need to
explicitly provide the splat operator:

irb(main):007:0> a,b=*[1,2]
=> [1, 2]
irb(main):008:0> a
=> 1
irb(main):009:0> b
=> 2

You can do a lot more fancy stuff with this as Ruby actually does
pattern matching:

irb(main):001:0> a,b,c=1,[2,3]
=> [1, [2, 3]]
irb(main):002:0> a
=> 1
irb(main):003:0> b
=> [2, 3]
irb(main):004:0> c
=> nil
irb(main):005:0> a,(b,c)=1,[2,3]
=> [1, [2, 3]]
irb(main):006:0> a
=> 1
irb(main):007:0> b
=> 2
irb(main):008:0> c
=> 3

This is often useful when using #inject on a Hash:

$ irb
irb(main):001:0> h={}
=> {}
irb(main):002:0> 10.times { i=rand(10); h[i]=10-i}
=> 10
irb(main):003:0> h
=> {0=>10, 6=>4, 7=>3, 2=>8, 8=>2, 3=>7, 9=>1, 4=>6}
irb(main):004:0> h.size
=> 8
irb(main):005:0> h.inject(0) {|sum,(key,val)| sum + key + val}
=> 80

This is of course a silly example. I chose these values in order to
make checking what’s going on easier (i.e. summing all keys and values
equals h.size * 10).

Kind regards

robert