Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort a list of objects by using their attributes in Ruby

I have a list of Fruit structs called basket. Each Fruit struct has a name (a string) and a calories (an integer). I would like to sort basket so that:

  1. The Fruits with the highest calories appear first. For example, a fruit with 500 calories appears before a fruit with 400 calories.

  2. If two Fruits have equal calories, the Fruit whose name comes first alphabetically comes first, ignoring case. For example, given two fruits with equal calories, one named "banana" will come before one named "Citrus".

The definition of Fruit is not something I control so I'd prefer a solution which doesn't involve mixing anything into Fruit or changing it. Is this possible?

like image 289
Kyle Kaitan Avatar asked May 12 '09 13:05

Kyle Kaitan


People also ask

How do you sort objects in Ruby?

The Ruby sort method works by comparing elements of a collection using their <=> operator (more about that in a second), using the quicksort algorithm. You can also pass it an optional block if you want to do some custom sorting. The block receives two parameters for you to specify how they should be compared.

How do you sort a list of objects based on an attribute of the objects in JavaScript?

In JavaScript, we use the sort() function to sort an array of objects. The sort() function is used to sort the elements of an array alphabetically and not numerically. To get the items in reverse order, we may use the reverse() method.

What sorting algorithm does Ruby use?

The Array#sort method in Ruby uses the venerable Quicksort algorithm. In its best case, Quicksort has time complexity O(n log n), but in cases where the data to be sorted is already ordered, the complexity can grow to O(n2).


2 Answers

The easy solution is

basket.sort_by { |f| [-f.calories, f.name] }

Of course, if this is the canonical sort order for fruit then it should be defined using the <=> method and with the Comparable module mixed into Fruit

like image 105
Gareth Avatar answered Sep 23 '22 01:09

Gareth


Let's assume that your basket is an Array or a subclass thereof.

The Fast Way

Enumerable.sort_by

As Gareth pointed out, Enumerable (included by Array) has a sort_by method that runs through each list item once. This is faster to run and faster to write once you get the hang of it.

# -f.calories to sort descending
# name.downcase to do a case-insensitive sort
basket = basket.sort_by { |f| [-f.calories, f.name.downcase] }

The Perl Way

Array.sort

Coming from a Perl background, my first impulse is to grab the spaceship operator <=>. Cheeky little devil. Array has the sort and sort! methods that make it very useful. This solution is slower, and because it's longer it is more likely to introduce bugs. The only reason to use it is if you're dealing with people unfamiliar with Ruby and unwilling to find the right way on StackOverflow.

baseket.sort! { |a,b|
  if a.calories == b.calories
    a.name.downcase <=> b.name.downcase
  else
    # Reverse the result to sort highest first.
    -(a.calories <=> b.calories)
  end
}
like image 35
Adrian Dunston Avatar answered Sep 24 '22 01:09

Adrian Dunston