检测重叠周期的算法[重复]

Posted

技术标签:

【中文标题】检测重叠周期的算法[重复]【英文标题】:Algorithm to detect overlapping periods [duplicate] 【发布时间】:2012-11-10 22:11:01 【问题描述】:

我必须检测两个时间段是否重叠。 每个时期都有一个开始日期和一个结束日期。 我需要检测我的第一个时间段 (A) 是否与另一个时间段 (B/C) 重叠。 就我而言,如果 B 的开头等于 A 的结尾,则它们不重叠(反之亦然) 我发现了以下案例:

所以实际上我是这样做的:

tStartA < tStartB && tStartB < tEndA //For case 1
OR
tStartA < tEndB && tEndB <= tEndA //For case 2
OR
tStartB < tStartA  && tEndB > tEndA //For case 3

(案例4在案例1或案例2中被记入帐户)

有效,但似乎效率不高。

首先,c# 中有一个现有的类可以对此(时间段)进行建模,类似于时间跨度,但具有固定的开始日期。

其次:是否已经有 c# 代码(如 DateTime 类)可以处理这个问题?

第三:如果不是,您会采取什么方法使这种比较最快?

【问题讨论】:

案例 5 中的句点 (C) 让我感到困惑。这是否代表不重叠的情况?如果是这样,您不会一分为二,案例 5 B 完全在 A 之前,案例 6 A 完全在 B 之前? 是的,它不重叠。 有一种情况 6,其中两个日期范围是相同的 - 接受的答案没有为这种情况提供正确的答案 - 如果您使用此解决方案,您可能需要考虑更新你的代码!! @DanB 实际上不编辑,如果我检查,我认为解决方案涵盖了这种情况:如果 a.startb.start 最终相等且相同,则您有:a.start &lt; a.end &amp;&amp; a.start &lt; a.end这是真的。 @J4N - 谢谢 - 我现在看到了... 【参考方案1】:

这是我的解决方案:

public static bool OverlappingPeriods(DateTime aStart, DateTime aEnd,
                                      DateTime bStart, DateTime bEnd)

    if (aStart > aEnd)
        throw new ArgumentException("A start can not be after its end.");

    if(bStart > bEnd)
        throw new ArgumentException("B start can not be after its end.");

    return !((aEnd < bStart && aStart < bStart) ||
                (bEnd < aStart && bStart < aStart));

我以 100% 的覆盖率对其进行了单元测试。

【讨论】:

【参考方案2】:

此代码检查两个间隔是否重叠。

---------|---|
---|---|                > FALSE
xxxxxxxxxxxxxxxxxxxxxxxxx
-------|---|
---|---|                > FALSE
xxxxxxxxxxxxxxxxxxxxxxxxx
------|---|
---|---|                > TRUE
xxxxxxxxxxxxxxxxxxxxxxxxx
---|---|
---|--|                 > TRUE
xxxxxxxxxxxxxxxxxxxxxxxxx
----|---|
---|-----|              > TRUE
xxxxxxxxxxxxxxxxxxxxxxxxx
---|---|
----|-|                 > TRUE
xxxxxxxxxxxxxxxxxxxxxxxxx
---|---|
----|--|                > TRUE
xxxxxxxxxxxxxxxxxxxxxxxxx
---|---|
---|---|                > TRUE
xxxxxxxxxxxxxxxxxxxxxxxxx
---|---|
----|---|               > TRUE
xxxxxxxxxxxxxxxxxxxxxxxxx
---|---|
-------|---|            > FALSE
xxxxxxxxxxxxxxxxxxxxxxxxx
---|---|
--------|---|           > FALSE

算法:

x1 < y2
and
x2 > y1

示例 12:00 - 12:30 不与 12:30 13:00 重叠

【讨论】:

在情况 2 中,第一个下边界等于第二个上边界,它表示 FALSE,我的意思是没有重叠。然而,在 10 的情况下,第一个上边界等于第二个下边界,它说 TRUE。这些在概念上并没有什么不同,除非你没有给第一个和第二个成员一些抽象的含义。我认为这会使该算法无效。 @Supernovah 算法有效。那 TRUE 可能只是一个错字。我已提交对此答案的修改,因此最后 2 个场景按预期显示 FALSE。【参考方案3】:

勾选这个简单的方法(建议把这个方法放在你的dateUtility

public static bool isOverlapDates(DateTime dtStartA, DateTime dtEndA, DateTime dtStartB, DateTime dtEndB)
        
            return dtStartA < dtEndB && dtStartB < dtEndA;
        

【讨论】:

???这与接受的答案相同。 它的优点是代码完美而不是一行,对于初学者来说意义重大 好吧,对我来说这是关于算法的,它适用于任何语言。【参考方案4】:

试试这个。此方法将确定(两个)日期跨度是否重叠,无论方法输入参数的顺序如何。这也可以用于两个以上的日期跨度,通过单独检查每个日期跨度组合(例如,对于 3 个日期跨度,运行 span1span2span2span3span1 对 @ 987654326@):

public static class HelperFunctions

    public static bool AreSpansOverlapping(Tuple<DateTime,DateTime> span1, Tuple<DateTime,DateTime> span2, bool includeEndPoints)
    
        if (span1 == null || span2 == null)
        
            return false;
        
        else if ((new DateTime[]  span1.Item1, span1.Item2, span2.Item1, span2.Item2 ).Any(v => v == DateTime.MinValue))
        
            return false;
        
        else
        
            if (span1.Item1 > span1.Item2)
            
                span1 = new Tuple<DateTime, DateTime>(span1.Item2, span1.Item1);
            
            if (span2.Item1 > span2.Item2)
            
                span2 = new Tuple<DateTime, DateTime>(span2.Item2, span2.Item1);
            

            if (includeEndPoints)
            
                return 
                ((
                    (span1.Item1 <= span2.Item1 && span1.Item2 >= span2.Item1) 
                    || (span1.Item1 <= span2.Item2 && span1.Item2 >= span2.Item2)
                ) || (
                    (span2.Item1 <= span1.Item1 && span2.Item2 >= span1.Item1) 
                    || (span2.Item1 <= span1.Item2 && span2.Item2 >= span1.Item2)
                ));
            
            else
            
                return 
                ((
                    (span1.Item1 < span2.Item1 && span1.Item2 > span2.Item1) 
                    || (span1.Item1 < span2.Item2 && span1.Item2 > span2.Item2)
                ) || (
                    (span2.Item1 < span1.Item1 && span2.Item2 > span1.Item1) 
                    || (span2.Item1 < span1.Item2 && span2.Item2 > span1.Item2)
                ) || (
                    span1.Item1 == span2.Item1 && span1.Item2 == span2.Item2
                ));
            
        
    

测试:

static void Main(string[] args)
  
    Random r = new Random();

    DateTime d1;
    DateTime d2;
    DateTime d3;
    DateTime d4;

    for (int i = 0; i < 100; i++)
    
        d1 = new DateTime(2012,1, r.Next(1,31));
        d2 = new DateTime(2012,1, r.Next(1,31));
        d3 = new DateTime(2012,1, r.Next(1,31));
        d4 = new DateTime(2012,1, r.Next(1,31));

        Console.WriteLine("span1 = " + d1.ToShortDateString() + " to " + d2.ToShortDateString());
        Console.WriteLine("span2 = " + d3.ToShortDateString() + " to " + d4.ToShortDateString());
        Console.Write("\t");
        Console.WriteLine(HelperFunctions.AreSpansOverlapping(
            new Tuple<DateTime, DateTime>(d1, d2),
            new Tuple<DateTime, DateTime>(d3, d4),
            true    //or use False, to ignore span's endpoints
            ).ToString());
        Console.WriteLine();
    

    Console.WriteLine("COMPLETE");
    System.Console.ReadKey();

【讨论】:

为什么投反对票?【参考方案5】:
--logic FOR OVERLAPPING DATES
DECLARE @StartDate datetime  --Reference start date
DECLARE @EndDate datetime    --Reference end date
DECLARE @NewStartDate datetime --New Start date
DECLARE @NewEndDate datetime   --New End Date

Select 
(Case 
    when @StartDate is null 
        then @NewStartDate
    when (@StartDate<@NewStartDate and  @EndDate < @NewStartDate)
        then @NewStartDate
    when (@StartDate<@NewStartDate and  @EndDate > @NewEndDate)
        then @NewStartDate
    when (@StartDate<@NewStartDate and  @EndDate > @NewStartDate)
        then @NewStartDate  
    when (@StartDate>@NewStartDate and  @NewEndDate < @StartDate)
        then @NewStartDate
    else @StartDate end) as StartDate,  

(Case 
    when @EndDate is null   
        then @NewEndDate
    when (@EndDate>@NewEndDate and @Startdate < @NewEndDate)
        then @NewEndDate
    when (@EndDate>@NewEndDate and @Startdate > @NewEndDate)
        then @NewEndDate
    when (@EndDate<@NewEndDate and @NewStartDate > @EndDate)
        then @NewEndDate
    else @EndDate end) as EndDate

Distrubution logic

【讨论】:

不确定您如何认为这个答案比我接受的答案更好?【参考方案6】:

我正在构建一个预订系统并找到了这个页面。我只对范围相交感兴趣,所以我建立了这个结构;玩 DateTime 范围就足够了。

您可以检查 Intersection 并检查特定日期是否在范围内,并获取 交集类型和最重要的:可以得到相交的Range。

public struct DateTimeRange


    #region Construction
    public DateTimeRange(DateTime start, DateTime end) 
        if (start>end) 
            throw new Exception("Invalid range edges.");
        
        _Start = start;
        _End = end;
    
    #endregion

    #region Properties
    private DateTime _Start;

    public DateTime Start 
        get  return _Start; 
        private set  _Start = value; 
    
    private DateTime _End;

    public DateTime End 
        get  return _End; 
        private set  _End = value; 
    
    #endregion

    #region Operators
    public static bool operator ==(DateTimeRange range1, DateTimeRange range2) 
        return range1.Equals(range2);
    

    public static bool operator !=(DateTimeRange range1, DateTimeRange range2) 
        return !(range1 == range2);
    
    public override bool Equals(object obj) 
        if (obj is DateTimeRange) 
            var range1 = this;
            var range2 = (DateTimeRange)obj;
            return range1.Start == range2.Start && range1.End == range2.End;
        
        return base.Equals(obj);
    
    public override int GetHashCode() 
        return base.GetHashCode();
    
    #endregion

    #region Querying
    public bool Intersects(DateTimeRange range) 
        var type = GetIntersectionType(range);
        return type != IntersectionType.None;
    
    public bool IsInRange(DateTime date) 
        return (date >= this.Start) && (date <= this.End);
    
    public IntersectionType GetIntersectionType(DateTimeRange range) 
        if (this == range) 
            return IntersectionType.RangesEqauled;
        
        else if (IsInRange(range.Start) && IsInRange(range.End)) 
            return IntersectionType.ContainedInRange;
        
        else if (IsInRange(range.Start)) 
            return IntersectionType.StartsInRange;
        
        else if (IsInRange(range.End)) 
            return IntersectionType.EndsInRange;
        
        else if (range.IsInRange(this.Start) && range.IsInRange(this.End)) 
            return IntersectionType.ContainsRange;
        
        return IntersectionType.None;
    
    public DateTimeRange GetIntersection(DateTimeRange range) 
        var type = this.GetIntersectionType(range);
        if (type == IntersectionType.RangesEqauled || type==IntersectionType.ContainedInRange) 
            return range;
        
        else if (type == IntersectionType.StartsInRange) 
            return new DateTimeRange(range.Start, this.End);
        
        else if (type == IntersectionType.EndsInRange) 
            return new DateTimeRange(this.Start, range.End);
        
        else if (type == IntersectionType.ContainsRange) 
            return this;
        
        else 
            return default(DateTimeRange);
        
    
    #endregion


    public override string ToString() 
        return Start.ToString() + " - " + End.ToString();
    

public enum IntersectionType

    /// <summary>
    /// No Intersection
    /// </summary>
    None = -1,
    /// <summary>
    /// Given range ends inside the range
    /// </summary>
    EndsInRange,
    /// <summary>
    /// Given range starts inside the range
    /// </summary>
    StartsInRange,
    /// <summary>
    /// Both ranges are equaled
    /// </summary>
    RangesEqauled,
    /// <summary>
    /// Given range contained in the range
    /// </summary>
    ContainedInRange,
    /// <summary>
    /// Given range contains the range
    /// </summary>
    ContainsRange,

【讨论】:

非常感谢您的实施。这是一个没有问题的综合实施。 这正是我所需要的。 ? 太棒了!谢谢【参考方案7】:
public class ConcreteClassModel : BaseModel

... rest of class

public bool InersectsWith(ConcreteClassModel crm)
    
        return !(this.StartDateDT > crm.EndDateDT || this.EndDateDT < crm.StartDateDT);
    


[TestClass]
public class ConcreteClassTest

    [TestMethod]
    public void TestConcreteClass_IntersectsWith()
    
        var sutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 02, 01), EndDateDT = new DateTime(2016, 02, 29) ;

        var periodBeforeSutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 01, 01), EndDateDT = new DateTime(2016, 01, 31) ;
        var periodWithEndInsideSutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 01, 10), EndDateDT = new DateTime(2016, 02, 10) ;
        var periodSameAsSutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 02, 01), EndDateDT = new DateTime(2016, 02, 29) ;

        var periodWithEndDaySameAsStartDaySutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 01, 01), EndDateDT = new DateTime(2016, 02, 01) ;
        var periodWithStartDaySameAsEndDaySutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 02, 29), EndDateDT = new DateTime(2016, 03, 31) ;
        var periodEnclosingSutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 01, 01), EndDateDT = new DateTime(2016, 03, 31) ;

        var periodWithinSutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 02, 010), EndDateDT = new DateTime(2016, 02, 20) ;
        var periodWithStartInsideSutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 02, 10), EndDateDT = new DateTime(2016, 03, 10) ;
        var periodAfterSutPeriod = new ConcreteClassModel()  StartDateDT = new DateTime(2016, 03, 01), EndDateDT = new DateTime(2016, 03, 31) ;

        Assert.IsFalse(sutPeriod.InersectsWith(periodBeforeSutPeriod), "sutPeriod.InersectsWith(periodBeforeSutPeriod) should be false");
        Assert.IsTrue(sutPeriod.InersectsWith(periodWithEndInsideSutPeriod), "sutPeriod.InersectsWith(periodEndInsideSutPeriod)should be true");
        Assert.IsTrue(sutPeriod.InersectsWith(periodSameAsSutPeriod), "sutPeriod.InersectsWith(periodSameAsSutPeriod) should be true");

        Assert.IsTrue(sutPeriod.InersectsWith(periodWithEndDaySameAsStartDaySutPeriod), "sutPeriod.InersectsWith(periodWithEndDaySameAsStartDaySutPeriod) should be true");
        Assert.IsTrue(sutPeriod.InersectsWith(periodWithStartDaySameAsEndDaySutPeriod), "sutPeriod.InersectsWith(periodWithStartDaySameAsEndDaySutPeriod) should be true");
        Assert.IsTrue(sutPeriod.InersectsWith(periodEnclosingSutPeriod), "sutPeriod.InersectsWith(periodEnclosingSutPeriod) should be true");

        Assert.IsTrue(sutPeriod.InersectsWith(periodWithinSutPeriod), "sutPeriod.InersectsWith(periodWithinSutPeriod) should be true");
        Assert.IsTrue(sutPeriod.InersectsWith(periodWithStartInsideSutPeriod), "sutPeriod.InersectsWith(periodStartInsideSutPeriod) should be true");
        Assert.IsFalse(sutPeriod.InersectsWith(periodAfterSutPeriod), "sutPeriod.InersectsWith(periodAfterSutPeriod) should be false");
    

感谢上述答案帮助我为 MVC 项目编写上述代码。

注意 StartDateDT 和 EndDateDT 是 dateTime 类型

【讨论】:

【参考方案8】:

有一个很棒的库,在 CodeProject 上有很好的评价:http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET

那个库在重叠、交叉等方面做了很多工作。它太大了,无法全部复制/粘贴,但我会看看哪些特定部分对你有用。

【讨论】:

【参考方案9】:

我不相信框架本身有这个类。也许是第三方库...

但是为什么不创建一个 Period 值对象类来处理这种复杂性呢?这样,您可以确保其他约束,例如验证开始与结束日期时间。比如:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Whatever.Domain.Timing 
    public class Period 
        public DateTime StartDateTime get; private set;
        public DateTime EndDateTime get; private set;

        public Period(DateTime StartDateTime, DateTime EndDateTime) 
            if (StartDateTime > EndDateTime)
                throw new InvalidPeriodException("End DateTime Must Be Greater Than Start DateTime!");
            this.StartDateTime = StartDateTime;
            this.EndDateTime = EndDateTime;
        


        public bool Overlaps(Period anotherPeriod)
            return (this.StartDateTime < anotherPeriod.EndDateTime && anotherPeriod.StartDateTime < this.EndDateTime)
        

        public TimeSpan GetDuration()
            return EndDateTime - StartDateTime;
        

    

    public class InvalidPeriodException : Exception 
        public InvalidPeriodException(string Message) : base(Message)      
    

这样你就可以单独比较每个时期...

【讨论】:

在这里试试codeproject.com/Articles/168662/Time-Period-Library-for-NET【参考方案10】:

您可以创建一个可重用的 Range 模式类:

public class Range<T> where T : IComparable

    readonly T min;
    readonly T max;

    public Range(T min, T max)
    
        this.min = min;
        this.max = max;
    

    public bool IsOverlapped(Range<T> other)
    
        return Min.CompareTo(other.Max) < 0 && other.Min.CompareTo(Max) < 0;
    

    public T Min  get  return min;  
    public T Max  get  return max;  

您可以添加合并范围、获取交点等所需的所有方法...

【讨论】:

好答案,但是比较应该是 return Min.CompareTo(other.Max) code public bool InersectsW 如何像autofac一样使用IoC注入这个类? 这个实现是不可变的,所以你可以注入它。我建议不要让它可变。如果你真的想注入它,声明一个接口,实现一个无参数的 Ctor 并使 Min 和 Max 属性可变【参考方案11】:

自定义interval-tree 结构怎么样?您必须对其稍作调整,以定义在您的域中“重叠”的两个间隔意味着什么。

This question 可能会帮助您在 C# 中找到现成的区间树实现。

【讨论】:

【参考方案12】:

简单检查两个时间段是否重叠:

bool overlap = a.start < b.end && b.start < a.end;

或在您的代码中:

bool overlap = tStartA < tEndB && tStartB < tEndA;

(如果您改变主意不想说两个刚刚接触的时期重叠,请使用&lt;= 而不是&lt;。)

【讨论】:

@J4N 我第一次必须这样做时,我编写了像你这样的代码,直到有人指出这一点。只是传递它:) 我看不出这如何涵盖所有场景。 @doker 是对称的。如果你交换 ab 你会得到相同的语句,只是切换了 &amp;&amp; 的任一侧。 @Rawling 我只是不明白,但它有效。你说的对。我最深切的敬意。 漂亮!它用“是的,如果两个人都在另一个人死之前出生”来回答“两个人是否见过面”。当您表达相反的意思时,起作用的原因变得清晰:“如果其中一方在另一方出生之前死亡,则不会。”实际上,仅测试案例 5:overlap = !(a.start &gt; b.end || b.start &gt; a.end)

以上是关于检测重叠周期的算法[重复]的主要内容,如果未能解决你的问题,请参考以下文章

算法在数据语料库中检测重复/相似的字符串——比如电子邮件主题,在 Python [重复]

相同(重复)代码的不同时钟周期值

检测重叠日期并更新最新记录SQL Server 2008

arcgis中如何删除重复线

寻找C++区间树算法实现[重复]

重复子串搜索