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:
<span class="no">LEVELS</span> <span class="o">=</span> <span class="o">[</span> <span class="s2">"not remotely cute"</span><span class="p">,</span> <span class="s2">"cute"</span><span class="p">,</span> <span class="s2">"kawaiiiii"</span><span class="p">,</span> <span class="s2">"awwwwwwwwwwwwwwww"</span><span class="o">]</span>
First thing first, we need to define a level
property on the Kitty
class.
<span class="no">Kitty</span> <span class="o">=</span> <span class="no">Struct</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:age</span><span class="p">,</span> <span class="ss">:cuteness</span><span class="p">,</span> <span class="ss">:level</span><span class="p">)</span>
Now, let's create some kittens and let's try to check who's the cutest.
<span class="n">maru</span> <span class="o">=</span> <span class="no">Kitty</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"maru"</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="s2">"awwwwwwwwwwwwwwww"</span><span class="p">)</span><span class="n">tard</span> <span class="o">=</span> <span class="no">Kitty</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"tard"</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="s2">"not remotely cute"</span><span class="p">)</span><span class="n">kitty</span> <span class="o">=</span> <span class="no">Kitty</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"kitty"</span><span class="p">,</span> <span class="mi">37</span><span class="p">,</span> <span class="mi">97</span><span class="p">,</span> <span class="s2">"kawaiiiii"</span><span class="p">)</span><span class="nb">puts</span> <span class="n">maru</span> <span class="o">></span> <span class="n">kitty</span><span class="nb">puts</span> <span class="n">tard</span> <span class="o">></span> <span class="n">kitty</span><span class="c1"># => undefined method `>' for #<Kitty:0x007fa99eaaa588> (NoMethodError)'`</span>
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:
<span class="no">Kitty</span> <span class="o">=</span> <span class="no">Struct</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:age</span><span class="p">,</span> <span class="ss">:cuteness</span><span class="p">,</span> <span class="ss">:level</span><span class="p">)</span> <span class="k">do</span> <span class="kp">include</span> <span class="no">Comparable</span> <span class="k">def</span> <span class="nf"><=></span><span class="p">(</span><span class="n">other</span><span class="p">)</span> <span class="no">LEVELS</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">level</span><span class="p">)</span> <span class="o"><=></span> <span class="no">LEVELS</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">other</span><span class="o">.</span><span class="n">level</span><span class="p">)</span> <span class="k">end</span><span class="k">end</span>
We include the Comparable
module, and implement a <=>
method comparing the
position of the kitty level to the LEVELS
definition.
<span class="n">maru</span> <span class="o">=</span> <span class="no">Kitty</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"maru"</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="s2">"awwwwwwwwwwwwwwww"</span><span class="p">)</span><span class="n">tard</span> <span class="o">=</span> <span class="no">Kitty</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"tard"</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="s2">"not remotely cute"</span><span class="p">)</span><span class="n">kitty</span> <span class="o">=</span> <span class="no">Kitty</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"kitty"</span><span class="p">,</span> <span class="mi">37</span><span class="p">,</span> <span class="mi">97</span><span class="p">,</span> <span class="s2">"kawaiiiii"</span><span class="p">)</span><span class="nb">p</span> <span class="n">maru</span> <span class="o">></span> <span class="n">kitty</span><span class="nb">p</span> <span class="n">tard</span> <span class="o">></span> <span class="n">kitty</span><span class="nb">p</span> <span class="n">kitty</span><span class="o">.</span><span class="n">between?</span><span class="p">(</span><span class="n">tard</span><span class="p">,</span> <span class="n">maru</span><span class="p">)</span><span class="c1"># => true</span><span class="c1"># => false</span><span class="c1"># => true</span>
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.
<span class="k">class</span> <span class="nc">Kitten</span> <span class="kp">include</span> <span class="no">Enumerable</span> <span class="kp">attr_reader</span> <span class="ss">:results</span> <span class="kp">private</span> <span class="ss">:results</span> <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">results</span><span class="p">)</span> <span class="vi">@results</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">(</span><span class="n">results</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">each</span> <span class="n">results</span><span class="o">.</span><span class="n">each</span> <span class="p">{</span><span class="o">|</span><span class="n">item</span><span class="o">|</span> <span class="k">yield</span> <span class="n">item</span> <span class="p">}</span> <span class="k">end</span><span class="k">end</span><span class="n">kittens</span> <span class="o">=</span> <span class="no">Kitten</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="o">[</span><span class="n">maru</span><span class="p">,</span> <span class="n">tard</span><span class="p">,</span> <span class="n">kitty</span><span class="o">]</span><span class="p">)</span><span class="nb">puts</span> <span class="s2">"Sorting"</span><span class="nb">puts</span> <span class="n">kittens</span><span class="o">.</span><span class="n">sort</span><span class="nb">puts</span> <span class="s2">"Cute"</span><span class="nb">puts</span> <span class="n">kittens</span><span class="o">.</span><span class="n">max</span><span class="nb">puts</span> <span class="s2">"Less cute"</span><span class="nb">puts</span> <span class="n">kittens</span><span class="o">.</span><span class="n">min</span><span class="c1"># => Sorting</span><span class="c1"># => #<struct Kitty name="tard", age=13, cuteness=42, level="not remotly even cute"></span><span class="c1"># => #<struct Kitty name="kitty", age=37, cuteness=97, level="kawaiiiii"></span><span class="c1"># => #<struct Kitty name="maru", age=8, cuteness=64, level="awwwwwwwwwwwwwwww"></span><span class="c1"># => Cute</span><span class="c1"># => #<struct Kitty name="maru", age=8, cuteness=64, level="awwwwwwwwwwwwwwww"></span><span class="c1"># => Less cute</span><span class="c1"># => #<struct Kitty name="tard", age=13, cuteness=42, level="not remotly cute"></span>
Thanks to modules like Enumerable
and Comparable
, Ruby provides us with some
tools to easily model our application domain.