11

Let's say I have the following array:

arr = [[5, 1], [2, 7]]

and I want to find the minimum element, comparing the second element of the elements. The minimum element will be [5, 1] since 1 is less than 7. I can use the following code:

arr.min {|a,b| a[1] <=> b[1]}

For calculating the maximum, I can do the same:

arr.max {|a,b| a[1] <=> b[1]}

That gives [2, 7].

I use the same block all the time. I would like to have that block somewhere and provide it to the min/max function. I hoped something like:

blo = lambda {|a,b| a[1] <=> b[1]}
arr.min blo

would work, but it didn't. Any idea on how I can do this?

sawa
  • 165,429
  • 45
  • 277
  • 381
Cristobal Viedma
  • 990
  • 1
  • 8
  • 20

5 Answers5

24

Use the & operator to turn a Proc object into a block.

arr.min &blo
sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • @Cristobal perhaps you should "accept" the answer you are satisfied with. It indicates to other users that this question has been answered and answer accepted. – Aditya Sanghi Dec 23 '10 at 11:43
  • Thanks @Aditya! I didn't know I had to accept answers. I will go and revise all my previous questions now :) – Cristobal Viedma Dec 23 '10 at 15:52
  • It is also the whole basis of StackOverflow's reputation system. Everytime you accept an answer to your question, the person who's answer you post, gets 15 reputation points and you (the person who accepts the answer) get 2. Reputation is the new Resume. – Aditya Sanghi Dec 23 '10 at 15:54
  • Thanks! I used your solution to find the longest length in a set of keys for string formatting. `max_key_length = params.keys.max{|a, b| a.length <=> b.length}.length`. Exactly what I needed. – Justin Force Nov 22 '11 at 17:48
14

@sepp2k's answer is the more general one, but in your specific case, I would just use

arr.min_by(&:last)
arr.max_by(&:last)

since that is much more obvious than all those curly braces and square brackets and array indices floating around.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • That's what I was looking for, so you can do `line_items.max_by(&:quantity)` for instance. Thanks a lot – Dorian Mar 24 '14 at 15:38
3

If all that you need is minimum and maximum, you might use Enumerable#minmax method and calculate both at once:

min, max = arr.minmax {|a,b| a[1] <=> b[1]}
#=> [[5, 1], [2, 7]]
min
#=> [5, 1]
max
#=> [2, 7]

Edit: Hell, I just noticed there is also minmax_by, so you can combine it with last method, and have:

min, max = arr.minmax_by &:last
Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
2

how about this?

=> [[5, 4], [9, 5], [2, 7]]
>> arr.sort!{|x,y| x[1]<=>y[1] }
=> [[5, 4], [9, 5], [2, 7]]
>> min,max=arr[0],arr[-1]
=> [[5, 4], [2, 7]]
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
2

A more general solution to problems like this is to avoid nested arrays entirely and use a class instead. You can then define the <=> operator for that class, giving you access to all the functions in the Comparable mixin (http://ruby-doc.org/core/classes/Comparable.html) gives you the <, <=, ==, >=, and > operators and the method 'between?'

This is just an example, in real life you would use classes that describe what they store:

class Duo

  include Comparable

  def initialize( a, b )
      @a = a
      @b = b
  end

  def <=>(rhs)
      @b <=> rhs.b
  end

end

If you have an array of Duo object you can then use the min, max, and sort functions without having to define the comparison operator. So...

@a = Duo.new( 1, 10 )
@b = Duo.new( 2, 5 )
@c = Duo.new( 3, 1 )

[ @a, @b, @c ].sort

would return the array [ @c, @b, @a ]

And

[@a, @b, @c].max

would return @a

This is much more the 'Ruby Way' than nested data-structures with logic that relies on positions in arrays. It takes slightly more work at the start, but you'll find it much better in the long run.

Ruby is a very object oriented programming language and provides very powerful tools for you to use. I thoroughly recommend reading a book like "The Ruby Programming Language" or "The Ruby Way" to get a proper overview of the power of the language.

Paul Leader
  • 1,015
  • 6
  • 9
  • The name `Duo` doesn't indicate it'd sort by the last element. What if someone else created a `Duo` that sorted by the first element for their part of the project, leading to two different `Duo`s? – Andrew Grimm Oct 18 '10 at 22:18
  • Well that's always going to be a problem in any code you write, if you create a class and someone else also modifies it then you have a problem. But that isn't a reason not to do it. Of course this was just an example. In real life you would have a much more descriptive class name that actually described what was being stored, not just a generic two item store. – Paul Leader Oct 18 '10 at 22:40
  • Hi, thank you very much for your reply. I like it a lot and I agree with you, on the long run sometimes is better. In this occasion for me however, it was quicker just to pass the block. – Cristobal Viedma Oct 19 '10 at 11:27