Yesterday I attended a talk about truth tables. It was based on the premise of an interview question and whether the candidates were able to model and represent in code the appropriate truth table. The problem question was this:

“write a method that receives the month and the year and outputs how many days there are in that month”.

Sounds easy, doesn’t it? We all know which months have 30 and which 31 days in them. Apart from February, that is. February usually has 28 days, except that in leap years it has 29.  How do we know which years are leap years? There are certain rules that allow us to determine leap years:

  • The year is evenly divisible by 4
  • If the year can be evenly divided by 100, it is NOT a leap year, unless
  • The year is also evenly divisible by 400. Then it is a leap year

To sum it up, a year is a leap year when

  1. it is evenly divided by 4 but NOT evenly divided by 100.
  2. It is evenly divided by 4, 100 AND 400.

The truth table for this problem would be:

Month  Year No of Days
jan, mar, may, jul, aug, oct, dec any 31
apr, jun, sep, nov any 30
feb year % 4 != 0 28
feb (year % 4 == 0) && (year % 100 != 0) 29
feb (year % 4 == 0) && (year % 100 == 0) && (year % 400 != 0) 28
feb (year % 4 == 0) && (year % 100 == 0) && (year % 400 == 0) 29

The speaker used PHP code for a working solution, using multi-conditional statements . Now we could use the same approach in Ruby with a multi-branch conditional or maybe a Case statement and that would work just as well.  But, this being Ruby, there’s always another way. We can leverage two powerful Ruby features to model our truth table as a Hash:

  1. Everything is an expression in Ruby.  I mean everything, and that includes Hash keys and values. Every statement gets evaluated to an object and that’s a beautiful thing.

  2. Ruby is great for List Comprehensions . I suppose that’s because of its Lisp influences but the fact is Ruby offers some great ways of making lists out of lists.

Knowing all this, we can write our method as follows:

1 def month_days(year, month)
2   h = {
3        %w(jan mar may jul aug oct dec) => 31,
4        %w(apr jun sep nov) => 30,
5        %w(feb) => ((year % 4 == 0) && (year % 400 == 0)) ||
6                                ((year % 4 == 0) && (year % 100 != 0)) ?
7                                29 : 28
8    }
9    h.select {|k, v| k.include? month}.values
10 end

We use Arrays for the Hash keys and we use the ternary operator as  a value for the february key.  Our returning object (line 10) is the value of a Hash key that is generated by filtering the original Hash’s keys (Arrays) based on the desired month. Let’s run it:

$> puts month_days 1900, 'feb'
=> 28
$> puts month_days 2000, 'feb'
=> 29
$> puts month_days 1900, 'sep'
=> 30

Beautiful. To me this looks much cleaner and elegant than a big If or Case  statement.  Of course, Ruby being Ruby, there’ll be a better way, so if you know of any feel free to share it with me by commenting below.