I have two arrays of date time and I want to union them.
list1 = [
{start: '2018-08-28 03:00:00', end: '2018-08-28 08:00:00'},
{start: '2018-08-28 11:00:00', end: '2018-08-28 13:00:00'},
{start: '2018-08-28 15:00:00', end: '2018-08-28 16:00:00'},
{start: '2018-08-28 16:15:00', end: '2018-08-28 17:00:00'},
{start: '2018-08-28 18:00:00', end: '2018-08-28 19:00:00'},
]
list2 = [
{start: '2018-08-28 03:00:00', end: '2018-08-28 09:00:00'},
{start: '2018-08-28 11:30:00', end: '2018-08-28 12:30:00'},
{start: '2018-08-28 15:15:00', end: '2018-08-28 16:15:00'},
]
The result is like
list3 = [
{start: '2018-08-28 03:00:00', end: '2018-08-28 09:00:00'},
{start: '2018-08-28 11:00:00', end: '2018-08-28 13:00:00'},
{start: '2018-08-28 15:00:00', end: '2018-08-28 17:00:00'},
{start: '2018-08-28 18:00:00', end: '2018-08-28 19:00:00'},
]
I tried in Ruby
list3 = list1.concat(list2).sort{|a, b| DateTime.parse(a[:start]) - DateTime.parse(b[:start]) }
puts result
and JavaScript but not success.
Here's one solution:
parse = ->(r) { DateTime.parse(r[:start])..DateTime.parse(r[:end]) }
ranges = list1.map(&parse) + list2.map(&parse)
merged = ranges.sort_by(&:begin).reduce([]) do |acc, range|
if acc.any? && acc.last.cover?(range.begin)
last = acc.pop
acc.push last.begin..range.end
else
acc.push range
end
acc
end
Your solution doesn't "merge" the time ranges, because it just sorts the list of times by ascending distance between start times. This will obviously not produce any useful merging of times!
I'm using Ruby's Range construct to create a time range from each start/end pair, then I sort all the ranges by their beginning time, then reduce the list of individual ranges with the algorithm:
if the current range covers the next range's start date:
extend the current range to the next range's end date
else
add the next range into the return list
It's worth noting that I'm just merging all the ranges via any intersections; there's no need to split them into two lists, because if there is an overlapping range in one list or the other, they would union together anyhow.
You don't have to use ranges, but they map onto this concept cleanly, and you can then use them to produce intervals or whatever you're looking to do.
Edit: As Cary pointed out, if the data contains an inverted range, the results get a little funky. Here's an alternate parsing scheme which addresses that issue:
ranges = [list1, list2].flatten.map(&:values).map do |v|
Range.new *v.map {|d| DateTime.parse d}.sort.take(2)
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With