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