0

I'm used to being able to shorten

some_array.map { |e| e.to_s }

to

some_array.map(&:to_s)

Is there a way to shorten

some_array_of_arrays.map { |e| e[4] }

similar to

some_array_of_arrays.map(&:[4])

Obviously I've tried that last example but it doesn't work. Ideally the solution would be generalized to other 'weirdly formatted' method calls like [].

I am not interested in any Rails/ActiveSupport solution. Plain Ruby only, assuming there is some sort of solution.

taylorthurlow
  • 2,953
  • 3
  • 28
  • 42
  • 2
    No, the shortcut only works for nullary methods (i.e. methods with no arguments). `:[]` can be called (because `e[4]` is equivalent to `e.[](4)`) but there is no way to pass the required parameter using the shortcut. – Amadan Oct 29 '18 at 04:59
  • 1
    This question has been asked many times. I expect someone will find a previous version of it in short order. – Cary Swoveland Oct 29 '18 at 05:06
  • @CarySwoveland [**ruby using the “&:methodname” shortcut from array.map(&:methodname) for hash key strings rather than methodname**](https://stackoverflow.com/q/20179636/479863) is sort of partially a duplicate. – mu is too short Oct 29 '18 at 05:39

2 Answers2

5

you can use Proc:

> a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14]]
> third_elem = Proc.new {|x| x[2]}
> a.map(&third_elem)
#> [3, 7, 11, nil] 

OR

> a.map &->(s) {s[2]}
#=> [3, 7, 11, nil] 
Gagan Gami
  • 10,121
  • 1
  • 29
  • 55
  • I think the spirit of the question was to find a way to do this that is more convenient than `.map { |e| e[4] }`, but I think this is as close as we'll get. – taylorthurlow Nov 03 '18 at 21:04
4

Then again, you can build it. It's not as elegant, but...

class Call
  def self.[](name, *args)
    self.new(name, *args)
  end

  def initialize(name, *args)
    @proc = Proc.new do |obj|
      obj.send(name, *args)
    end
  end

  def to_proc
    @proc
  end
end

fourth = Call.new(:[], 3)
[[1,2,3,4,5],[6,7,8,9,10]].map(&fourth)           # => [4, 9]
# or equivalently
[[1,2,3,4,5],[6,7,8,9,10]].map(&Call.new(:[], 3)) # => [4, 9]
[[1,2,3,4,5],[6,7,8,9,10]].map(&Call[:[], 3])     # => [4, 9]

If you want to specialise it for indexes, you could even simplify to this:

class Index
  def self.[](*args)
    self.new(*args)
  end

  def initialize(*args)
    @proc = Proc.new do |obj|
      obj[*args]
    end
  end

  def to_proc
    @proc
  end
end

[[1,2,3,4,5],[6,7,8,9,10]].map(&Index[3])     # => [4, 9]

Or, much shorter, as @muistooshort demonstrated in comments, if you don't want to have a full class dedicated to it:

index = ->(*ns) { ->(a) { a[*ns] } }
[[1,2,3,4,5],[6,7,8,9,10]].map(&index[3])     # => [4, 9]
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • You could replace `Index` with `index = ->(n) { ->(a) { a[n] } }` and say `array_of_arrays.map(&index[3])`. – mu is too short Oct 29 '18 at 05:37
  • 1
    @muistooshort It is too short! :D Great, though it would not work for subsequences (while my `&Index[1, 3]` works). – Amadan Oct 29 '18 at 05:39