Reputation: 3123
Ruby max_by method finds the maximal element form an array. Sometimes the maximal elements are with multipicity, in this case max_by chooses only one of them, seemingly arbitrarily. When I need all of them, I use this approach currently, to find the maximal values in an array of arrays:
sorted=ary.sort_by{|a,b| b}.reverse
max_score=sorted.first[1]
t=sorted.take_while{|z| z[1]==max_score}
But how could I monkey-patch the Array class with a "maxes_by" method, which accept a block, similarly to max_by, and returns an array of the maximal values?
Upvotes: 2
Views: 2810
Reputation: 106962
Without writing a new, optimized method that returns the expected output, you can combine max_by
and select
:
maximum = array.max_by { |element| element[1] }
t = array.select { |element| element[1] == maximum[1] }
Another option might be to group all elements by the value in question (with group_by
) and then just pick the list with the max value.
lists = array.group_by { |element| element[1] }
lists[lists.keys.maximum]
Upvotes: 6
Reputation: 110725
It's worth a mention that the task could be done in a single pass through the array, or more generally, in a single pass through any collection whose class includes Enumerable
.
module Enumerable
def max_by_all
return each unless block_given?
last_yield = nil
each_with_object([]) do |e,a|
ye = yield(e)
case last_yield.nil? ? -1 : last_yield <=> ye
when -1
a.replace([e])
last_yield = ye
when 0
a << e
end
end
end
end
arr = [2, 4, 3, 4, 1, 2, 5, 3, 5, 1]
arr.max_by_all(&:itself)
#=> [5, 5]
arr = ["style", "assets", "misty", "assist", "corgi", "bossy", "bosses", "chess"]
arr.max_by_all { |s| s.count('s') }
#=> ["assets", "assist", "bosses"]
h = { a: 1, b: 3, c: 2, d: 3, e: 1 }
h.max_by_all(&:last)
#=> [[:b, 3], [:d, 3]]
arr = [1, 2, 3]
arr.max_by_all.map { |n| 2*n }
#=> [2, 4, 6]
In the last example max_by_all
has no block and therefore returns an enumerator which merely enumerates the elements of self
. This behaviour may seem pointless but I've provided for it (the line return each unless block_given?
) to mimic the behaviour of Enumerable#max_by when no block is provided.
Upvotes: 1
Reputation: 2194
class Array
def maxes_by
maximal = max_by { |x| yield(x) }
select { |x| yield(x) == yield(maximal) }
end
end
> ['house', 'car', 'mouse'].maxes_by { |x| x.length }
=> ['house', 'mouse']
But I don't recommend to monkey patch the Array
class, this practice is dangerous and can potentially lead to undesirable effects on your system.
For our good, ruby language provides a nice feature to overcome this problem, the Refinements, which is a safe way for monkey patching on ruby.
To simplify, with the Refinements you can monkey patch the Array
class and the changes will only be available inside the scope of the class that is using the refinement! :)
You can use the refinement inside the class you are working on and you are ready to go.
module MaxesByRefinement
refine Array do
def maxes_by
maximal = max_by { |x| yield(x) }
select { |x| yield(x) == yield(maximal) }
end
end
end
class MyClass
using MaxesByRefinement
def test
a = %w(house car mouse)
a.maxes_by { |x| x.length } # maxes_by is available here!
end
end
> MyClass.new.test
=> ['house', 'mouse']
Upvotes: 0