Sorting a Ruby Array by two attributes

Posted by Miguel Rentes on June 8, 2015
Read more about ruby, sorting

If you ever need to sort a Ruby array of objects by two of its attributes, very much like using an SQL statement with the Order By keyword:

SELECT * from <Table> ORDER BY column1, column2

you can get the same results in Ruby with the simple code:

array.sort! do |a, b|
    comp = (a.attribute1 <=> b.attribute1)
    if comp.zero?
        a.attribute2 <=> b.attribute2
	end
	comp	
end

In this example, array is an array of objects containing several attributes and the sort method matches any two objects on the array, checking first if they have the same attribute1 attribute. If they are the same, hence resulting in a comparison equal to zero, then the sort is done by the attribute2. Hence, we get the “ORDER BY” functionality.

We can even had another sorting criteria, if the two objects have different attribute1 values:

array.sort do |a, b|
    comp = (a.attribute1 <=> b.attribute1)
    if comp.zero?
        a.attribute2 <=> b.attribute2
	else
	    a.attribute3 <=> b.attribute3
    end
	comp
end

As a concrete example, imagine we have an array of Bug objects with the following states:

irb(main):001:0> class Bug
irb(main):002:1> attr_accessor :status_id, :id, :subject
irb(main):003:1> def initialize status, id, subject
irb(main):004:2> @status_id = status
irb(main):005:2> @id = id
irb(main):006:2> @subject = subject
irb(main):007:2> end
irb(main):008:1> end
=> :initialize
irb(main):009:0> array = [Bug.new(5, 13942, "Crash of component A"), Bug.new(5, 14563, "A nasty bug"), Bug.new(2, 12031, "Another nasty bug found"), Bug.new(2, 57786, "Implement TDD on the code"), Bug.new(1, 987, "Read all about Ruby code")]
=> [#<Bug:0x000000021e50a8 @status_id=5, @id=13942, @subject="Crash of component A">, #<Bug:0x000000021e4fe0 @status_id=5, @id=14563, @subject="A nasty bug">, #<Bug:0x000000021e4f40 @status_id=2, @id=12031, @subject="Another nasty bug found">, #<Bug:0x000000021e4ec8 @status_id=2, @id=57786, @subject="Implement TDD on the code">, #<Bug:0x000000021e4e78 @status_id=1, @id=987, @subject="Read all about Ruby code">]

As you can see, our array is at the moment with the following objects:

[
 #<Bug:0x000000021e50a8 @status_id=5, @id=13942, @subject="Crash of component A">,
 #<Bug:0x000000021e4fe0 @status_id=5, @id=14563, @subject="A nasty bug">,
 #<Bug:0x000000021e4f40 @status_id=2, @id=12031, @subject="Another nasty bug found">,
 #<Bug:0x000000021e4ec8 @status_id=2, @id=57786, @subject="Implement TDD on the code">,
 #<Bug:0x000000021e4e78 @status_id=1, @id=987, @subject="Read all about Ruby code">
]

So let’s implement our sorting and see how the array gets sorted:

irb(main):010:0> array.sort do |a, b|
irb(main):011:1* comp = (a.status_id <=> b.status_id)
irb(main):012:1> if comp.zero?
irb(main):013:2> a.id <=> b.id
irb(main):014:2> end
irb(main):015:1> comp
irb(main):016:1> end
=> [#<Bug:0x000000021e4e78 @status_id=1, @id=987, @subject="Read all about Ruby code">, #<Bug:0x000000021e4f40 @status_id=2, @id=12031, @subject="Another nasty bug found">, #<Bug:0x000000021e4ec8 @status_id=2, @id=57786, @subject="Implement TDD on the code">, #<Bug:0x000000021e50a8 @status_id=5, @id=13942, @subject="Crash of component A">, #<Bug:0x000000021e4fe0 @status_id=5, @id=14563, @subject="A nasty bug">]

As you can see, the array is now sorted like this:

[
 #<Bug:0x000000021e4e78 @status_id=1, @id=987, @subject="Read all about Ruby code">,
 #<Bug:0x000000021e4f40 @status_id=2, @id=12031, @subject="Another nasty bug found">,
 #<Bug:0x000000021e4ec8 @status_id=2, @id=57786, @subject="Implement TDD on the code">,
 #<Bug:0x000000021e50a8 @status_id=5, @id=13942, @subject="Crash of component A">,
 #<Bug:0x000000021e4fe0 @status_id=5, @id=14563, @subject="A nasty bug">
]

If you want to make the sorting effects stored into the array, replace sort with sort!.

For larger objects when comparisons can become relatively expensive, an alternative to this code is using the sort_by method, which has performance improvements over the traditional sort. The above code then simply becomes:

irb(main):017:0> array.sort_by do |x|
irb(main):018:1* [x.status_id, x.id]
irb(main):019:1> end

Again, if you want to make the sorting stored into the array, replace sort_by with sort_by!.

I hope this post has helped you in learning how to sort an array of objects using two attributes from the objects contained in the array. To sum everything up, I share with you the links that served as an inspiration for this post.

Until next time, have a lot of coding fun!