Vijay Dev
Vijay Dev

Reputation: 27476

How to access arrays within arrays using Ruby's Symbol#to_proc?

How do you write the following using the Symbol#to_proc syntax?

[[a,1],[b,2],[c,3]].map { |r| r[0] }

This gives an error that the number of arguments is wrong. But I do not know where to give the index argument.

[[a,1],[b,2],[c,3]].map &:[]  # Where to put the index 0 ?

I prefer the first form, and I just want to know how to do it using &:.

Upvotes: 2

Views: 624

Answers (4)

Henrik N
Henrik N

Reputation: 16284

Most likely overkill, but for the sake of completeness: you could write your own method like

class Array
  def to_proc
    ->(x) { x[first] }
  end
end

and use it like so:

[[a,1],[b,2],[c,3]].map(&[0])

You can read more about this in my blog post "Array#to_proc for hash access".

Upvotes: 0

Mladen Jablanović
Mladen Jablanović

Reputation: 44080

You can't. You can pass only methods without parameters that way. In your case you need to suply 0 as a parameter, which you can only using block syntax.

On the other hand, you can achieve the same using first method:

[[a,1],[b,2],[c,3]].map(&:first)

edit:

Both Joerg's and tokland's answers inspired me to experiment a little bit. You can actually do something like this:

arr = [[:a, 1], [:b, 2], [:c, 3]]
arr.each_with_object(0).map(&:[])
#=> [:a, :b, :c]
arr.each_with_object(1).map(&:[])
#=> [1, 2, 3]

Upvotes: 6

Jörg W Mittag
Jörg W Mittag

Reputation: 369468

Symbol#to_proc takes the first argument to be the receiver and passes on any extra arguments it receives to the method, but in this case it gets passed only one argument, because Enumerable#map only passes the element as the only argument.

However, if that only argument happens to be an Array, then we can use the destructuring bind feature of block argument binding! So, we can simply use Enumerable#zip to zip together our elements with an infinite list of 0s! This, of course, requires that we use something which has block argument binding semantics, i.e. either a block proper:

[[a,1],[b,2],[c,3]].zip([0].cycle).map {|el, i| el[i]}

or a non-lambda Proc

f = proc {|a, i| a[i] }

[[a,1],[b,2],[c,3]].zip([0].cycle).map(&f)

It does, however, not work with Methods or lambdas, because those don't have block argument binding semantics, they have method argument binding semantics, thus they don't perform automatic destructuring. Symbol#to_proc, of course, does a message send, thus ending up invoking a method and not a non-lambda Proc or a block. That's why this won't work with Symbol#to_proc.

Upvotes: 3

tokland
tokland

Reputation: 67870

Short answer: you can't.

Long answer:

  1. Write your own smart Array#to_proc: array.map(&[:[], 0]). Pretty ugly in this particular case.

  2. Use Facets map_send:

array.map_send(:[], 0)

(and 3: as Mladen pointed out: array.map(&:first), but if you want another index you can't)

Upvotes: 0

Related Questions