How to pass a custom comparator to “sort”?

≡放荡痞女 提交于 2019-11-30 01:34:46

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!