Skip to content

Instantly share code, notes, and snippets.

@benjamin-hull
Created August 1, 2019 12:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benjamin-hull/3ffbd5b58b3f6667d3c35b75bd637620 to your computer and use it in GitHub Desktop.
Save benjamin-hull/3ffbd5b58b3f6667d3c35b75bd637620 to your computer and use it in GitHub Desktop.
Grouping overlapping ranges
# A dumb helper to check the overlap. You can use Rails' built-in 'overlaps?' if you like
def range_overlap?(r1, r2)
r1.cover?(r2.first) || r2.cover?(r1.first)
end
# Times are expressed here as a number of minutes since midnight, which is just
# a way of ignoring the date part of a dateTime
times = [(600..660), (630..690), (690..705), (840..900)]
# First, create an array of the outer edges of each group, expressed as ranges
groups = times.reduce([]) do |a, r|
new_group = !a.each_with_index.any? do |existing, i|
if range_overlap?(existing, r)
# Over-write the array index with the expanded range if required:
# a[i] will 'grow' to encompass the r
a[i] = ([r.min, existing.min].min)..([r.max, existing.max].max)
# Once we've found one, bail since there won't be any other matches
break true
end
end
a << r if new_group
a
end
puts groups.inspect # => [600..705, 840..900]
# Next, put each actual time into its group.
result = groups.reduce(Hash.new { |h, k| h[k] = [] }) do |a, g|
times.each do |r|
a[g] << r if range_overlap?(g, r)
end
a
end
puts result # => {600..705=>[600..660, 630..690, 690..705], 840..900=>[840..900]}
puts result.values.inspect # => [[600..660, 630..690, 690..705], [840..900]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment