19

I want to specify a custom block method to sort an object array by evaluating two properties. However, after many searches, I didn't find to any example without the <=> operator.

I want to compare a to b:

if a.x less than b.x return -1
if a.x greater than b.x return 1
if a.x equals b.x, then compare by another property , like a.y vs b.y

This is my code and it doesn't work:

ar.sort! do |a,b|
   if a.x < b.y return -1
   elseif a.x > b.x return 1
   else return a.y <=> b.y
end

This block is within a function return is exiting the function and returning -1.

5
  • why don't you use sort method? Commented Mar 14, 2013 at 4:19
  • @Gashner I'm using it, but I'm intended to customize the evaluation. Commented Mar 14, 2013 at 5:32
  • Why is <=> ruled out? Because you want to do it the hard way? Commented Mar 14, 2013 at 7:35
  • The question is already answered but I want to point out that the problem with your code is due to wrong if syntax. You can simply change it to ``` if a.x < b.x return -1 elsif a.x > b.x return 1 else return a.x <=> b.x end ``` Make sure to end the if block. Also you are sometimes using b.y instead of b.x. Commented Dec 12, 2018 at 0:32
  • can someone explain me shortly what is (a.x, a.y, b.x and b.y). what is the use of that in .sort method . thank you for your response . Commented Oct 12, 2022 at 9:25

4 Answers 4

33

This will give you ascending order for x then for y:

points.sort_by{ |p| [p.x, p.y] }
Sign up to request clarification or add additional context in comments.

3 Comments

+1 for sort_by and Array#<=> which has exactly the semantics OP desires.
This is very elegant! Well done - should be the accepted answer, IMHO
sort_by will be slower for small arrays. Toss a big array or more complex accesses to the values that are the sort keys, and it can quickly outrun regular sort routines.
24

The best answer is provided by @AJcodez below:

points.sort_by{ |p| [p.x, p.y] }

The "correct" answer I originally provided, while it technically works, is not code I would recommend writing. I recall composing my response to fit the question's use of if/else rather than stopping to think or research whether Ruby had a more expressive, succinct way, which, of course, it does.


With a case statement:

ar.sort do |a, b|
  case
  when a.x < b.x
    -1
  when a.x > b.x
    1
  else
    a.y <=> b.y
  end
end 

With ternary:

ar.sort { |a,b| a.x < b.x ? -1 : (a.x > b.x ? 1 : (a.y <=> b.y)) }

2 Comments

A multi-level ternary statement in ruby is discouraged for readability/maintenance reasons.
The answer below by AJcodez is better than mine here; use that instead.
0

This seems to work:

# Ascending
ar.sort! do |a,b|
  (a-b).abs() > 0 ? a-b : 0
end

# Descending
ar.sort! do |a,b|
  (a-b).abs() > 0 ? b-a : 0
end

There's no need for the spaceship operator ( <=> ) - it seems to be superfluous in Ruby (in terms of syntax efficiency), because Ruby considers 0 to be true, and for this usage specifically (above), you can just put the explicit 0 in the ternary operator.

1 Comment

sort! isn't sort and will modify the receiver (ar in this case). While sort! is faster, that behavior is something to be aware of.
0

We can do it with more simple way

ar.sort! do |a,b|
  [a.x, a.y] <=> [b.x, b.y]
end

Or

ar.sort! do |a,b|
  result = a.x <=> b.x
  next if result != 0

  a.y <=> b.y
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.