How does Array.map work for 2D arrays?

K

Karthik Nar

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:
=> ["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?
 
R

Robert Klemme

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:
=> ["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?

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
 
K

karthik

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?!)
 
R

Robert Klemme

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=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
 
D

Daniel Waite

Karthik said:
... a two-parameter block suppled to map like below:
=> ["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?
 
K

Karthik Nar

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?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top