Enumerable#sort_by
We'll wrap up Enumerable week with Enumerable#sort_by.
Many programming languages provide a method to sort collections based on the value of each element. E.g. [3,1,2].sort() would yield [1,2,3] and ["c", "a", "b"].sort() would yield ["a", "b", "c"].
Unfortunately, this is rarely the sorting we actually need in real-life scenarios. More commonly we need to sort based on some property or derived value of each element, e.g. sorting a list of users by their age or sorting products by their price.
As luck would have it, Ruby provides sort_by for exactly this purpose. You provide a block that computes a value for each element, and sort_by sorts the collection based on those computed values.
Product = Struct.new(:name, :price)
products = [
Product.new("Apple", 1.2),
Product.new("Banana", 0.8),
Product.new("Cherry", 2.5),
]
alphabetical = products.sort_by(&:name)
# => [
# #<struct Product name="Apple", price=1.2>,
# #<struct Product name="Banana", price=0.8>,
# #<struct Product name="Cherry", price=2.5>,
# ]
cheapest_first = products.sort_by(&:price)
# => [
# #<struct Product name="Banana", price=0.8>,
# #<struct Product name="Apple", price=1.2>,
# #<struct Product name="Cherry", price=2.5>,
# ]
const products = [
{ name: "Apple", price: 1.2 },
{ name: "Banana", price: 0.8 },
{ name: "Cherry", price: 2.5 },
];
const alphabetical = products
.slice() // slice to avoid mutating the original array 🙈
.sort((a, b) => a.name.localeCompare(b.name));
// => [
// { name: "Apple", price: 1.2 },
// { name: "Banana", price: 0.8 },
// { name: "Cherry", price: 2.5 },
// ]
const cheapestFirst = products
.slice()
.sort((a, b) => a.price - b.price);
// => [
// { name: "Banana", price: 0.8 },
// { name: "Apple", price: 1.2 },
// { name: "Cherry", price: 2.5 },
// ]
History
The method was added to Ruby in version 1.8.0, released in 2003. Building on top of the already existing sort method.
I did a quick search on a 20-year-old Ruby on Rails codebase I often work on and found more than 200 occurrences of sort_by and only a single occurrence of sort. This makes for a good example of how Ruby evolved to include the right abstractions for real-world use cases.
Under the hood the method uses the Schwartzian transform algorithm, popularized in the Perl community. The official Ruby documentation explicitly mentions this, suggesting that Ruby originally got its inspiration from Perl.
This marks the end of our first Enumerable week. Given that there are still plenty more nice methods to cover I wouldn't be surprised if we see another Enumerable week before the year is over. Stay tuned!