Created
August 1, 2019 12:51
-
-
Save benjamin-hull/3ffbd5b58b3f6667d3c35b75bd637620 to your computer and use it in GitHub Desktop.
Grouping overlapping ranges
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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