Python日期区间交集

20

出于一般性的兴趣,我想知道是否有更优雅/高效的方法来完成这个任务。我有一个函数,它比较两个起始/结束日期元组,如果它们相交,则返回 true。

from datetime import date
def date_intersection(t1, t2):
    t1start, t1end = t1[0], t1[1]
    t2start, t2end = t2[0], t2[1]

    if t1end < t2start: return False
    if t1end == t2start: return True
    if t1start == t2start: return True
    if t1start < t2start and t2start < t1end: return True
    if t1start > t2start and t1end < t2end: return True
    if t1start < t2start and t1end > t2end: return True
    if t1start < t2end and t1end > t2end: return True
    if t1start > t2start and t1start < t2end: return True
    if t1start == t2end: return True
    if t1end == t2end: return True 
    if t1start > t2end: return False

所以如果:

d1 = date(2000, 1, 10)
d2 = date(2000, 1, 11)
d3 = date(2000, 1, 12)
d4 = date(2000, 1, 13)

然后:

>>> date_intersection((d1,d2),(d3,d4))
False
>>> date_intersection((d1,d2),(d2,d3))
True
>>> date_intersection((d1,d3),(d2,d4))
True

等等。

我很好奇是否有更Pythonic/优雅/高效/简洁/通常更好的方法来使用mxDateTime或一些巧妙的timedelta或set()?

如果找到交集,函数返回开始/结束元组的另一种有用形式。

谢谢。


你应该使用时间增量来表示t1 ⇔ t2的持续时间,我正在考虑下一个部分。http://docs.python.org/library/datetime.html#datetime.timedelta - msw
7个回答

29

它并不真的更Pythonic,但是你可以简化判断交集的逻辑。这个问题经常出现:

return (t1start <= t2start <= t1end) or (t2start <= t1start <= t2end)
为了理解这为什么起作用,请考虑两个区间可能相交的不同方式,并看到其中一个的起始点必须始终在另一个的范围内。

是的!非常好。虽然它让我有点晕眩,但你已经将我的详尽案例分析简化成了一个非常清晰的表达式。非常感谢,这有助于澄清我的思路。 - jjon
根据我的单元测试,如果t1的结尾是t2的开头,则它无法正常工作。 - David Dahan
当 t2start <= t1end 时?我认为你应该再检查一下你的单元测试。 - Andrew

18

另一种并希望更易理解的解决方案:

def has_overlap(a_start, a_end, b_start, b_end):
    latest_start = max(a_start, b_start)
    earliest_end = min(a_end, b_end)
    return latest_start <= earliest_end

我们可以很容易地得到重叠部分的区间,即(latest_start, earliest_end)。请注意,最晚开始时间可以等于最早结束时间。

需要注意的是,这假设a_start <= a_endb_start <= b_end


2
这个是最易读的。而且,你可以轻松地返回 (earliest_end-latest_start) 来得到总重叠量的度量。 - cxrodgers
1
那是最好的答案! - SeF

7

这里有一个版本可以给你交集的范围。在我看来,它可能不是最优化的条件数量,但它清楚地显示了t2何时与t1重叠。如果你只想要真/假,你可以根据其他答案进行修改。

if (t1start <= t2start <= t2end <= t1end):
    return t2start,t2end
elif (t1start <= t2start <= t1end):
    return t2start,t1end
elif (t1start <= t2end <= t1end):
    return t1start,t2end
elif (t2start <= t1start <= t1end <= t2end):
    return t1start,t1end
else:
    return None

1
FYI... 链式比较运算符:https://dev59.com/oHVD5IYBdhLWcg3wE3No#101945 - fseto
非常感谢您提供链接以了解操作符链,这非常有帮助。 - jjon
可以使用min函数来美化结束日期。例如,如果start1 <= start2 <= end1,则返回(start2,min(end1,end2))。 - JeremyKun

4
intersection_length = min(t1end, t2end) - max(t1start, t2start)
return intersection_length >= 0 

如果交集只包含一个点,则使用> 0,否则不用。

直觉上讲,如果存在一个相交的区间,那么它的起始点应该是最高的起始点,结束点应该是最低的结束点。有两种可能的情况:

  1. 这是一个正确的区间,长度可能为零。

  2. 起始点和结束点被交换了。这意味着两个区间之间存在一个间隔,你可以称之为“负交集”。


3
Final Comparison: start <= other_finish and other_start <= finish

# All of the conditions below result in overlap I have left out the non overlaps

start <= other_start | start <= other_finish | other_start <= finish | finish <= other_finish 

      0                        1                        1                        0                   
      0                        1                        1                        1
      1                        1                        1                        0          
      1                        1                        1                        1

只有当起始时间 <= 另一个任务结束时间另一个任务起始时间 <= 结束时间时,才需要返回重叠。


一个好的答案应该包括一些解释,为什么这会解决问题。 - Qirel

0
非常晚的回答,但在我看来,最简单和最不费脑子的解决方案是:
def date_intersection(t1, t2):
    t1, t2 = sorted([t1, t2], key=lambda t: t[0])
    t1end = t1[1]
    t2start = t2[0]
    return t1 end <= t2start

我可能也会考虑将“时间间隔”的概念表达为一个类,这样我就可以做出更像这样的事情:

def date_intersection(interval1, interval2):
    i1, i2 = sorted([interval1, interval2], key=lambda i: i.start)
    return i1.end <= i2.start

0
if t1end < t2start or t1start > t2end: return False
if t1start <= t2end or t2start <= t1start: return True
return False

那不会覆盖所有相交的集合吗?


好的,你的代码简化为返回 t1end >= t2start and t1start <= t2end。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接