I have two lists of datetime
ranges.
eg.
l1 = [(datetime.datetime(2018, 8, 29, 1, 0, 0), datetime.datetime(2018, 8, 29, 3, 0, 0)), (datetime.dat
Your definition of union and intersection for the date range can be simply described as :-
Union:
In []:
from itertools import product
[(min(s1, s2), max(e1, e2)) for (s1, e1), (s2, e2) in product(l1, l2) if s1 <= e2 and e1 >= s2]
Out[]:
[(datetime.datetime(2018, 8, 29, 1, 0), datetime.datetime(2018, 8, 29, 4, 0)),
(datetime.datetime(2018, 8, 29, 5, 0), datetime.datetime(2018, 8, 29, 9, 0))]
Intersection:
In []:
[(max(s1, s2), min(e1, e2)) for (s1, e1), (s2, e2) in product(l1, l2) if s1 <= e2 and e1 >= s2]
Out[]:
[(datetime.datetime(2018, 8, 29, 2, 0), datetime.datetime(2018, 8, 29, 3, 0)),
(datetime.datetime(2018, 8, 29, 6, 0), datetime.datetime(2018, 8, 29, 7, 0))]
You can replace <=
and >=
with <
and >
if they strictly have to overlap and not just touch.
The answer here is very useful for what you're asking, as it can compact an array of overlapping ranges:
from operator import itemgetter
def consolidate(intervals):
sorted_intervals = sorted(intervals, key=itemgetter(0))
if not sorted_intervals: # no intervals to merge
return
# low and high represent the bounds of the current run of merges
low, high = sorted_intervals[0]
for iv in sorted_intervals[1:]:
if iv[0] <= high: # new interval overlaps current run
high = max(high, iv[1]) # merge with the current run
else: # current run is over
yield low, high # yield accumulated interval
low, high = iv # start new run
yield low, high # end the final run
Union of l1
and l2
is simply the consolidation of all ranges in both l1
and l2
:
def union(l1, l2):
return consolidate([*l1, *l2])
Intersection of l1
and l2
is adequately done by AChampion's code (if there is any overlap between any range in l1
and any range in l2
, that overlap deserves to be in the result), but it could lead to fragmentation of ranges; we can use this same function to join the adjacent or overlapping ranges:
from itertools import product
def intersection(l1, l2):
result = ((max(s1, s2), min(e1, e2)) for (s1, e1), (s2, e2) in product(l1, l2) if s1 < e2 and e1 > s2)
return consolidate(result)
An example:
l1 = [(1, 7), (4, 8), (10, 15), (20, 30), (50, 60)]
l2 = [(3, 6), (8, 11), (15, 20)]
print(list(union(l1, l2))) # [(1, 30), (50, 60)]
print(list(intersection(l1, l2))) # [(3, 6), (10, 11)]
(The example uses integers for clarity, but it works with any comparable type. Specifically, for OP's l1
and l2
, the code yields OP's desired datetime
results.)