Sandwich Scheduling: a look at Ruby's Array cycle method
- By Tim Ash
- 28 September
How Ruby can help you schedule your sandwiches
Tim's written a short guide to the Comparable module found in Ruby.
One of my favourite Ruby features is the Comparable module. By mixing this module into your classes, you can compare your objects using the ==
, <
, >
, <=
, >=
operators, as well as the between?
and clamp
methods. This allows you to quickly add some useful functionality with the minimum of coding effort. Hopefully, the following simple example will demonstrate some of the power of the Comparable module.
Let’s assume we are writing an application which deals with a football league consisting of a number of football teams. Each team’s placing in the league is determined by the number of points they have been awarded, say three points for a win and a single point for a draw. In this situation, it would be very easy to compare two team’s relative placings in the league. For example, your code might look like the following:
def FootballTeam
attr_accessor :points
def initialize points
@points = points
end
end
The relative placings of two teams can be determined using their points values. Let’s create a couple of teams and assign them some points:
chelsea = FootballTeam.new(10)
arsenal = FootballTeam.new(12)
In this case, chelsea.points > arsenal.points
would evaluate to false, while arsenal.points <= chelsea.points
would evaluate to true.
This is all very well, but let’s assume that if two teams have the same number of points, their relative placings in the league are determined by their goal difference - the number of goals the team has scored minus the number of goals the team has conceded. Our code might change to the following:
def FootballTeam
attr_accessor :points, :goals_scored, :goals_conceded
def initialize points, goals_scored, goals_conceded
@points = points
@goals_scored = goals_scored
@goals_conceded = goals_conceded
end
def goal_difference
@goals_scored - @goals_conceded
end
end
We’ve added code to allow the number of goals scored and the number of goals conceded to be set when the team object is created, and a method to allow the goal difference to be calculated. However, we can no longer determine two team’s relative placings simply by comparing their points - we need to take goal difference into account as well. One approach would be to override each of the comparison operators, so that for example we would add:
def > other_team
if self.points == other_team.points
self.goal_difference > other_team.goal_difference
else
self.points > other_team.points
end
end
Similar methods would be required for <
, ==
, <=
, and >=
However, this is where the Comparable module can help us. To use the Comparable module, we simply need to add the line include Comparable in our class definition, and add a method called <=> (sometimes known as the spaceship operator). The module will handle everything else, and will provide the other comparison methods. This sounds a little too good to be true, so let’s update the code, and see the Comparison module in action:
def FootballTeam
include Comparable
attr_accessor :points, :goals_scored, :goals_conceded
def initialize points, goals_scored, goals_conceded
@points = points
@goals_scored = goals_scored
@goals_conceded = goals_conceded
end
def goal_difference
@goals_scored - @goals_conceded
end
def <=> other_team
if self.points < other_team.points
-1
elsif self.points > other_team.points
1
else
if self.goal_difference < other_team.goal_difference
-1
elsif self.goal_difference > other_team.goal_difference
1
else
0
end
end
end
end
(We’ll refactor this in a moment.)
Now we can set up a few teams and compare them:
chelsea = FootballTeam.new(10, 11, 13)
arsenal = FootballTeam.new(12, 12, 9)
liverpool = FootballTeam.new(12, 12, 10)
chelsea > arsenal
will evaluate to false, while arsenal >= chelsea
will evaluate to true. Additionally, liverpool < arsenal
will evaluate to true, and liverpool.between?(chelsea, arsenal)
will also evaluate to true.
We’ve gained quite a lot of useful functionality by defining a single function.
We can now refine our code using the spaceship operator inside our <=>
method:
def <=> other_team
result = self.points <=> other_team.points
if result == 0
result = self.goal_difference <=> other_team.goal_difference
end
result
end
Hopefully, this post has given you an insight into why I’m a big fan of the Comparable module, and given you some ideas of how you can make use of it in your own code. I’d love to hear about the uses other coders have put it to - why not leave a comment?
How Ruby can help you schedule your sandwiches
Another way Ruby can help you keep track of your lockdown exercise routine
How Ruby can help you keep track of your lockdown exercise routine
How Ruby can help you mix the drinks and bust some moves at the charity ball
You won't believe what happens when all the animals run away from the zoo.
Home, home on the Range, where the Ewoks and the Wookies play
We would love to hear from you so let's get in touch!