wintermute
wintermute

Reputation: 516

Passing a comparison operator as argument to a method in Ruby

What would be the best way to pass a comparison operator as an argument to a method in Ruby? I wanted to create a generic sorted? recognizer for arrays, and pass either '<=', or '>=' to this method.

So far I have this code:

class Array
  def sorted?
    return 1 if sorted_increasing?
    return -1 if sorted_decreasing?
    0
  end

  def sorted_increasing?
    return true if length < 2
    self[0] <= self[1] && self.drop(1).sorted_increasing?
  end

  def sorted_decreasing?
    return true if length < 2
    self[0] >= self[1] && self.drop(1).sorted_decreasing?
  end
end

– it appears that it would be better to have a sorted_generic?(comparison_operator) method instead of sorted_increasing? and sorted_decreasing?.

Upd: Thank you for your replies, my solution looks as follows:

class Array
  def sorted?
    return 1 if sorted_generic?(:<=)
    return -1 if sorted_generic?(:>=)
    0
  end

  def sorted_generic?(comparison)
    return true if length < 2
    self[0].send(comparison, self[1]) &&
        self.drop(1).sorted_generic?(comparison)
  end
end

Upvotes: 0

Views: 850

Answers (3)

engineersmnky
engineersmnky

Reputation: 29598

As @CarySwoveland mentioned: You could also just use Enumerable methods #each_cons and #all?

However I would take this a step further to allow a default and custom sorted? mechanism.

class Array 
   def sorted?(sym=:<=,&block)
     block ||= ->(a,b){ a.public_send(sym,b) } 
     each_cons(2).all?(&block)
   end
end

This allows you the flexibility to call sorted? and use the anticipated increasing order,pass a symbol similar to reduce or inject for basic comparisons or pass a block to determine what sorted means to you.

This does not create any intermediary Arrays in the process and short circuits on the first failure.

This now no longer limits you to basic operations e.g.

  array_of_hashes = [{n:9,a:1},{n:14,a:-3},{n:11, a:0}]
  array_of_hashes.sorted?
  #=> false
  array_of_hashes.sorted? do |a,b| 
    a.values.reduce(:+) <= b.values.reduce(:+)
  end
  #=> true 

Additionally you could add some shortcut flavors like

class Array 
   def sorted_increasing? 
     sorted?(:<=)
   end 
   def sorted_decreasing? 
     sorted?(:>=)
   end
end

Such that

a = [1,2,3,4] 
a.sorted? == a.sorted_increasing?
#=> true

Upvotes: 1

Cary Swoveland
Cary Swoveland

Reputation: 110755

Though not a direct response to your question, I suggest a different approach.

class Array
  def sorted?(direction)
    sorted = self.sort
    case direction
    when :increasing
      self == sorted ? 1 : 0
    when :decreasing 
      self == sorted.reverse ? -1 : 0
    else
      # <raise exception>
    end
  end
end

[1,2,3,4].sorted?(:increasing) #=>  1
[4,3,2,1].sorted?(:increasing) #=>  0
[1,3,2,4].sorted?(:increasing) #=>  0
[1,1,1,1].sorted?(:increasing) #=>  1
[1,2,3,4].sorted?(:decreasing) #=>  0
[4,3,2,1].sorted?(:decreasing) #=> -1
[1,3,2,4].sorted?(:decreasing) #=>  0
[1,1,1,1].sorted?(:decreasing) #=> -1

Another way is to use Enumerable#each_cons.

class Array
  def sorted?(op)
    each_cons(2).all? { |e,f| e.public_send(op, f) } ? (op == :<= ? 1 : -1) : 0
  end
end

[1,2,3,4].sorted?(:<=) #=>  1
[4,3,2,1].sorted?(:<=) #=>  0
[1,3,2,4].sorted?(:<=) #=>  0
[1,1,1,1].sorted?(:<=) #=>  1
[1,2,3,4].sorted?(:>=) #=>  0
[4,3,2,1].sorted?(:>=) #=> -1
[1,3,2,4].sorted?(:>=) #=>  0
[1,1,1,1].sorted?(:>=) #=> -1

Upvotes: 3

Simple Lime
Simple Lime

Reputation: 11090

The comparison operators are just methods in Ruby, so you could do:

1 <= 2 # is the same as
1.<=(2)

which means you can public_send them just as any other public method:

1.public_send(:<=, 2)

Upvotes: 4

Related Questions