Huy Tran
Huy Tran

Reputation: 1902

Sort objects in array by 2 attributes in Ruby with the spaceship operator

I have a SizeMatters class that creates an object from a given string. In order to sort these objects in an array, I've implemented the <=>(other) method. But the following code only helps the objects to be sorted by size. I also want the array to be sorted alphabetically.

class SizeMatters
  include Comparable
  attr :str
  def <=>(other)
    str.size <=> 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("aaa")
s5 = SizeMatters.new("bbb")
s6 = SizeMatters.new("WWWW")
s7 = SizeMatters.new("VVVVV")

[ s3, s2, s5, s4, s1 , s6, s7].sort #[Z, YY, bbb, xXX, aaa, WWWW, VVVVV]

What I want is this

[ s3, s2, s5, s4, s1 , s6, s7].sort #[Z, YY, aaa, bbb, xXX, WWWW, VVVVV]

How can I write <=>(other) so that objects in array can be sorted first by size and then alphabetically?

Upvotes: 1

Views: 679

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110725

You said you want to sort the strings by size and break ties by sortings strings of the same length by lexicographical ("dictionary") order. Yes, you will need to define SizeMatters#<=>, but it may be a mistake to define it for sorting, as that would prevent you from comparing stings in the normal way elsewhere in your class. Consider keeping your definition of <=> and use Enumerable#sort_by for the sorting.

class SizeMatters
  include Comparable

  attr_reader :str

  def initialize(str)
    @str = str
  end

  def <=>(other)
    str.size <=> other.str.size
  end

  def sort_criteria
    [str.size, str]
  end

  def lexi_precede?(other)
    str < other.str
  end
end

[s3, s2, s5, s4, s1 , s6, s7].sort_by(&:sort_criteria).map(&:str)
  #=> ["Z", "YY", "aaa", "bbb", "xXX", "WWWW", "VVVVV"]

s1.lexi_precede?(s2)
  #=> false

Upvotes: 1

steenslag
steenslag

Reputation: 80075

Define <=> like this:

   def <=>(other)
     [str.size, str] <=> [other.str.size, other.str]
   end

Upvotes: 6

Related Questions