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:
The Fruit
s with the highest calories
appear first. For example, a fruit with 500 calories appears before a fruit with 400 calories.
If two Fruit
s 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?
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.
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.
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).
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
Let's assume that your basket is an Array or a subclass thereof.
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] }
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With