af83

Comparing and sorting elements with Comparable

Our previous feature "Building a collection of Kittens" was a huge success for our application. So, the product owner decided to bring this collection of kittens to a brand new level of awesomeness. Our mission is to add a level property to a Kitty, and allow to sort kittens based on this property.

Because everyone wants to know who's the cutest between Maru, Kitty and Tard.

Levels are defined in a very scientific way, as you can see below:

LEVELS = [
  "not remotely cute",
  "cute",
  "kawaiiiii",
  "awwwwwwwwwwwwwwww"
]

First thing first, we need to define a level property on the Kitty class.

Kitty = Struct.new(:name, :age, :cuteness, :level)

Now, let's create some kittens and let's try to check who's the cutest.

maru   =  Kitty.new("maru", 8, 64, "awwwwwwwwwwwwwwww")
tard   =  Kitty.new("tard", 13, 42, "not remotely cute")
kitty  =  Kitty.new("kitty", 37, 97, "kawaiiiii")

puts maru > kitty
puts tard > kitty
# =>  undefined method `>' for #<Kitty:0x007fa99eaaa588> (NoMethodError)'`

Obviously, Ruby does not know how to compare two kittens. But, it provides us with tools to do just that.

Comparable

Comparable is a module which provides the following methods:

  • <
  • <=
  • ==
  • >
  • >=
  • between?

Exactly what we needed to sort out and compare our kittens.

In the same way that Enumerable needs us to define each, Comparable needs us to define <=>. Some modifications to the Kitty class must be made:

Kitty = Struct.new(:name, :age, :cuteness, :level) do
  include Comparable

  def <=>(other)
    LEVELS.index(level) <=> LEVELS.index(other.level)
  end
end

We include the Comparable module, and implement a <=> method comparing the position of the kitty level to the LEVELS definition.

maru   =  Kitty.new("maru", 8, 64, "awwwwwwwwwwwwwwww")
tard   =  Kitty.new("tard", 13, 42, "not remotely cute")
kitty  =  Kitty.new("kitty", 37, 97, "kawaiiiii")

p maru > kitty
p tard > kitty
p kitty.between?(tard, maru)
# => true
# => false
# => true

Obviously, Maru is the cutest, and Tard is not.

Comparable and Enumerable

By adding Comparable to Kitty, we can now sort kittens from the Kitten class of our previous blog post.

class Kitten
  include Enumerable

  attr_reader :results
  private     :results

  def initialize(results)
    @results = Array(results)
  end

  def each
    results.each {|item| yield item }
  end
end

kittens = Kitten.new([maru, tard, kitty])
puts "Sorting"
puts kittens.sort
puts "Cute"
puts kittens.max
puts "Less cute"
puts kittens.min
# => Sorting
# => #<struct Kitty name="tard", age=13, cuteness=42, level="not remotly even cute">
# => #<struct Kitty name="kitty", age=37, cuteness=97, level="kawaiiiii">
# => #<struct Kitty name="maru", age=8, cuteness=64, level="awwwwwwwwwwwwwwww">
# => Cute
# => #<struct Kitty name="maru", age=8, cuteness=64, level="awwwwwwwwwwwwwwww">
# => Less cute
# => #<struct Kitty name="tard", age=13, cuteness=42, level="not remotly cute">

Thanks to modules like Enumerable and Comparable, Ruby provides us with some tools to easily model our application domain.

blog comments powered by Disqus