Saying stuff about stuff.

Challenge: find the longest time gap

Very occasionally I’m inspired by a code challenge, this time it’s finding the longest gap in minutes between consecutive timestamps (via @andycroll). Here’s my approach:

def find_longest_time_gap(times)
  times
    .map { _1.split(':', 2).map(&:to_i) }
    .map { |(h, m)| h * 60 + m }
    .each_cons(2)
    .map { |(a, b)| b - a }
    .max
end

find_longest_time_gap(['08:00', '10:00', '10:00', '14:00'])
# => 240

Breaking it down.

I’m using a “numbered parameter” _1 (which I kinda like more than the newer it 😬 though I’m not sure I’d use either in “proper” code). This is split into two-and-only-two parts (it’s maybe a bit defensive but I like that it’s explicit) and turned into integers to get an array of hours/minutes tuples:

.map { _1.split(':', 2).map(&:to_i) }
# => [[8, 0], [10, 0], [10, 0], [14, 0]]

They’re converted to minutes. Here I’m expanding each two-element tuple/array block argument into two variables (I’m not sure what this is called) which is helpful in these simple cases because it turns it into a neat single line:

.map { |(h, m)| h * 60 + m }
# => [480, 600, 600, 840]

Ruby has a wealth of useful methods – I’m thinking of chunk_while – so I knew there would be something to help me out. I cheated a little and peeked at Andy’s solution 👀 which revealed that this time it’s each_cons (its name was bound to contain an underscore).

each_cons is one of those methods I find slightly weird. It’s called “each” which suggests a side-effect rather than something being returned, but calling it without a block returns an Enumerator which I think I’d want more often (map_cons?).

Here’s what each_cons(n) does:

# Original array
[1, 2, 3, 4, 5]

# each_cons(2)
[1, 2]
   [2, 3]
      [3, 4]
         [4, 5]

# each_cons(3)
[1, 2, 3]
   [2, 3, 4]
      [3, 4, 5]

So here’s the each_cons(2) magic:

.each_cons(2)
# => [[480, 600], [600, 600], [600, 840]]

Now we can map over these to get the differences:

.map { |(a, b)| b - a }
# => [120, 0, 240]

And all that remains is the call to max.

.max
# => 240