检测重叠周期的算法[重复]
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.start
和 b.start
最终相等且相同,则您有:a.start < a.end && a.start < 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 个日期跨度,运行 span1
对 span2
、span2
对 span3
和 span1
对 @ 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;
(如果您改变主意不想说两个刚刚接触的时期重叠,请使用<=
而不是<
。)
【讨论】:
@J4N 我第一次必须这样做时,我编写了像你这样的代码,直到有人指出这一点。只是传递它:) 我看不出这如何涵盖所有场景。 @doker 是对称的。如果你交换a
和 b
你会得到相同的语句,只是切换了 &&
的任一侧。
@Rawling 我只是不明白,但它有效。你说的对。我最深切的敬意。
漂亮!它用“是的,如果两个人都在另一个人死之前出生”来回答“两个人是否见过面”。当您表达相反的意思时,起作用的原因变得清晰:“如果其中一方在另一方出生之前死亡,则不会。”实际上,仅测试案例 5:overlap = !(a.start > b.end || b.start > a.end)
以上是关于检测重叠周期的算法[重复]的主要内容,如果未能解决你的问题,请参考以下文章