2

Array#max_by returns only a single value, but I want to have all values that have the max value.

hashes = [{a: 1, b:2}, {a:2, b:3}, {a:1, b:3}]
max = hashes.map{|h| h[:b]}.max
hashes.select{|h| h[:b] == max}
# => [{a: 2, b: 3}, {a: 1, b: 3}]

This code works fine, and I want to add it to Array class.

class Array
  def max_values_by(&proc)
    max = map(&proc).max
    # I don't know how to use `select` here.
  end
end

How to access the value of the &proc argument?

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
ironsand
  • 14,329
  • 17
  • 83
  • 176

2 Answers2

2

Use the proc in the block passed to select by calling it with call:

class Array
  def max_values_by(&proc)
    max = map(&proc).max
    select { |h| proc.call(h) == max }
  end
end
hashes.max_values_by { |h| h[:b] }
=> [{a: 2, b: 3}, {a: 1, b: 3}]

or with yield, which gives identical results:

def max_values_by(&proc)
  max = map(&proc).max
  select { |h| yield(h) == max }
end

Although proc.call is a little longer than yield, I prefer it in this case because it makes it clearer that the same block is being used in two places in the method, and because it's weird to use both the implicit block passing of yield and the explicit passing of &proc in the same method.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
  • You can also use `yield(h)` instead of `proc.call(h)` – Stefan May 15 '16 at 06:37
  • Added, thanks. There is `Proc#yield` too, but that's just confusing. – Dave Schweisguth May 15 '16 at 06:56
  • 1
    You think `Proc#yield`'s confusing? You can also call the proc with `(proc === h)` (and with `[]`, as mentioned by the OP, and by the depricated-but-still-with-us `proc.(h)`). – Cary Swoveland May 15 '16 at 07:42
  • @CarySwoveland I didn't know that `.()` is deprecated. I googled but could not find anything about it. Could you point me to where I could find out more about it? – Keith Bennett May 15 '16 at 12:42
  • 1
    @Keith, maybe I was wrong about that. I thought I had read that but can't find a reference. `Proc#()` is not listed as a [Proc](http://ruby-doc.org/core-2.2.0/Proc.html) instance method in v2.2.0, but that doc also states, "prc.() invokes prc.call() with the parameters given. It’s a syntax sugar to hide 'call'". – Cary Swoveland May 16 '16 at 03:23
1

@DaveSchweisguth suggests a great implementation using select, like you requested. Another way of achieving the same result is by using group_by, like this:

>> hashes.group_by{|h| h[:b]}.max.last
=> [{:a=>2, :b=>3}, {:a=>1, :b=>3}]

or monkey-patched into Array as:

class Array
  def max_values_by(&proc)
    group_by(&proc).max.last
  end
end
user12341234
  • 6,573
  • 6
  • 23
  • 48
  • That is not what the OP asked. But is the same as the accepted answer to this question: http://stackoverflow.com/questions/22115956. – sawa May 15 '16 at 05:59