Class A
has the following comparator:
class A
attr_accessor x
def my_comparator(a)
x**2 <=> (a.x)**2
end
end
I would like to use this comparator to sort an array where each item is of class A:
class B
def my_method
items.sort!(<how can I pass my_comparator here ?>)
end
end
How should I pass my_comparator
to sort!
?
Define your own <=>
, and include Comparable. This is from the Comparable doc:
class SizeMatters
include Comparable
attr :str
def <=>(an_other)
str.size <=> an_other.str.size
end
def initialize(str)
@str = str
end
def inspect
@str
end
end
s1 = SizeMatters.new("Z")
s2 = SizeMatters.new("YY")
s3 = SizeMatters.new("XXX")
s4 = SizeMatters.new("WWWW")
s5 = SizeMatters.new("VVVVV")
s1 < s2 #=> true
s4.between?(s1, s3) #=> false
s4.between?(s3, s5) #=> true
[ s3, s2, s5, s4, s1 ].sort #=> [Z, YY, XXX, WWWW, VVVVV]
You don't actually have to include Comparable, but you get extra functionality for free if you do that after having defined <=>
.
Otherwise, you can use Enumerable's sort
with a block if your objects implement <=>
already.
Another way to use several different comparisons is to use lambdas. This uses the new 1.9.2 declaration syntax:
ascending_sort = ->(a,b) { a <=> b }
descending_sort = ->(a,b) { b <=> a }
[1, 3, 2, 4].sort( & ascending_sort ) # => [1, 2, 3, 4]
[1, 3, 2, 4].sort( & descending_sort ) # => [4, 3, 2, 1]
foo = ascending_sort
[1, 3, 2, 4].sort( & foo ) # => [1, 2, 3, 4]
Both of these should work:
items.sort_by! { |a| (a.x)**2 }
items.sort! { |a1,a2| a1.my_comparator(a2) }
items.sort!(&:my_comparator)
This calls the :my_comparator.to_proc
internally, which returns a block
proc {|x,y| x.my_comparator(y)}
thus reducing this answer to Ben Alpert's answer.
(But I agree with Phrogz's observation that if this is the natural order for the class, then you should use the Tin Man's answer instead.)
If you want to reuse these comparators in different places, it would be better to define them as a class, instead of rewriting the same lambda expression every time.
This is based on Java's implementation of Comparable interface:
module Comparator
def compare(a, b)
raise NotImplementedError, 'must implement this method'
end
def to_proc
->(a, b) { compare(a, b) }
end
end
class LengthComparator
include Comparator
def compare(a, b)
a.length <=> b.length
end
end
class ReverseLengthComparator < LengthComparator
def compare(a, b)
-super
end
end
You implement your comparison logic in the #compare method. You can then use this class like so: array.sort(&MyCustomComparator.new)
. It essentially boils down to a lambda expression, but supports more reusability in my opinion.
来源:https://stackoverflow.com/questions/4622206/how-to-pass-a-custom-comparator-to-sort