Reputation: 516
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
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 Array
s 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
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
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