【问题标题】:How to find gaps between multiple, variable DateRanges in C#如何在 C# 中查找多个变量 DateRanges 之间的间隙
【发布时间】:2020-07-16 08:50:27
【问题描述】:

我正在做一个约会查找器,您可以在其中输入多个用户的约会,它应该会在具有多个可变 DateRanges 的列表中找到空白。不幸的是,我写的方法不起作用。如果没有像 TimePeriodLibrary 这样的大型库,我怎么能做到这一点。

List<DateRange> appointments = new List<DateRange>();

appointments.Add(new DateRange(new DateTime(2020, 7, 6, 10, 30, 0), new DateTime(2020, 7, 6, 11, 30, 0)));

appointments.Add(new DateRange(new DateTime(2020, 7, 7, 8, 30, 0), new DateTime(2020, 7, 7, 15, 30, 0)));

appointments.Add(new DateRange(new DateTime(2020, 7, 6, 16, 30, 0), new DateTime(2020, 7, 6, 17, 0, 0)));

var gaps = FindGapsInUsersCalendars(appointments, 60);

if (gaps.Any())
{
    foreach (var gap in gaps)
    {
        Console.WriteLine($"{gap.Start} - {gap.End}");
    }
}



private static List<DateRange> FindGapsInUsersCalendars(List<DateRange> appointments, int minutes)
{
    List<DateRange> possibilities = new List<DateRange>();

    foreach (var appointment in appointments)
    {
        if (!DateRangeIncludesDateForMinutes(appointment, appointment.End, minutes)) continue;

        possibilities.Add(new DateRange(appointment.Start, appointment.Start.AddMinutes(minutes)));
    }

    return possibilities;
}

private static bool DateRangeIncludesDateForMinutes(DateRange dateRange, DateTime date, int minutes)
{
    var tempDate = date;

    for (var i = 0; i < minutes; i++)
    {
        if (!dateRange.Includes(tempDate.AddMinutes(1))) {
            return false;
        }
    }

    return true;
}

DateRange.cs 类:

public class DateRange : IRange<DateTime>
    {
        public DateRange(DateTime start, DateTime end)
        {
            Start = start;
            End = end;
        }

        public DateTime Start { get; private set; }
        public DateTime End { get; private set; }

        public bool Includes(DateTime value)
        {
            return (Start <= value) && (value <= End);
        }

        public bool Includes(IRange<DateTime> range)
        {
            return (Start <= range.Start) && (range.End <= End);
        }
    }

IRange.cs 接口:

public interface IRange<T>
    {
        T Start { get; }
        T End { get; }
        bool Includes(T value);
        bool Includes(IRange<T> range);
    }

【问题讨论】:

  • “不幸的是,我写的方法不起作用。”你希望他们做什么?他们在做什么呢?
  • @SomeBody 我希望DateRangeIncludesDateForMinutes 方法返回一个DateRange 是否至少包含方法args 中的分钟值。 FindGapsInUsersCalendars 应该返回一个列表,其中包含未定义约会的所有间隙。
  • 这能回答你的问题吗? Find gap between multiple dates in C#
  • @MaciejLos 不再,(我已经问过这个问题了)。不幸的是,我不能使用这个库。

标签: c# .net datetime math date-range


【解决方案1】:

我没有时间为此创建代码,但这里有一个相对简单的算法,您可以尝试实施以便在每次约会后找出差距:

  • 通过增加开始时间对所有约会进行排序。
  • 迭代约会,并为每个约会: 搜索开始时间小于或等于当前约会的结束时间的任何约会。
    • 如果是这样:冲突的约会是在当前约会之前还是同时结束?
      • 如果是这样,忽略它并继续寻找其他有冲突的约会。
      • 如果不是(它在当前约会之后结束),您知道在当前约会之后没有空闲时间,所以请中断并跳到检查下一个约会。
        • 实际上你可以跳到有冲突的约会,因为你已经知道至少在那个约会结束之前不会有任何休息)。
    • 如果不是:恭喜,您找到了一个约会,之后有一些空闲时间!

提示:将每个约会可视化为时间线,同时考虑这一点,并寻找它们重叠的地方:

|-------------|
    |--------|
       |------------|
                |----------| 
                  |-------| 
                             <free time>  
                                         |-----------|
                                              |------------|

【讨论】:

  • 宝贵的指导!
【解决方案2】:

这是我的解决方案。它使用我自己的具有 Max/Min 而不是 Start/End 的通用范围类型。但我希望它足够清楚,可以理解。

整体做法是将所有重叠的范围合并成连续的、不重叠的范围,然后简单地返回每个后续范围之间的空间。我建议多写一些测试,可能有一些我没有考虑过的极端情况。

    public bool Intersects<T>(Range<T> left, Range<T> right) where T : IComparable<T>
    {
        return !(left.Max.CompareTo(right.Min) < 0 ||
            right.Max.CompareTo(left.Min) < 0);
    }

    public Range<T> Union<T>(Range<T> left, Range<T> right) where T : IComparable<T>
    {
        var min = left.Min.CompareTo(right.Min) < 0 ? left.Min : right.Min;
        var max = left.Max.CompareTo(right.Max) > 0 ? left.Max : right.Max;
        return new Range<T>(min, max);
    }
    public List<Range<T>> Merge<T>(IEnumerable<Range<T>> ranges) where T : IComparable<T>
    {
        var orderedRanges = ranges.OrderBy(r => r.Max).ToList();
        for (int i = orderedRanges.Count-2; i >= 0; i--)
        {
            var current = orderedRanges[i + 1];
            var previous = orderedRanges[i];
            if (Intersects(current, previous))
            {
                var union = Union(current, previous);
                orderedRanges[i] = union;
                orderedRanges.RemoveAt(i+1);
            }
        }
        return orderedRanges;
    }

    public IEnumerable<Range<T>> Gaps<T>(IEnumerable<Range<T>> ranges) where T : IComparable<T>
    {
        var merged = Merge(ranges).OrderBy(r => r.Max).ToList();

        for (int i = 0; i < merged.Count-1; i++)
        {
            var current = merged[i];
            var next = merged[i + 1];
            yield return new Range<T>(current.Max, next.Min);
        }
    }

测试用例:

    [Test]
    public void TestGaps()
    {
        var sut = new[]
        {
            new Range<int>(0, 4),
            new Range<int>(1, 2),
            new Range<int>(3, 6),
            new Range<int>(5, 10),
            // Gap
            new Range<int>(12, 15),
            new Range<int>(13, 14),
            // Gap
            new Range<int>(20, 25),
        };
        var results = Gaps(sut);
        var expected = new[]
        {
            new Range<int>(10, 12),
            new Range<int>(15, 20),
        };

        CollectionAssert.AreEqual(expected, results);
    }

编辑: 我的范围类型的显着差异是使用通用约束来确保可以比较值。它也是一个避免分配的结构。我使用的签名是

public readonly struct Range<T> : where T : IComparable<T> 
{
    public T Min { get; }
    public T Max { get; }
    public Range(T min, T max) => (Min, Max) = (min, max);
    // Bunch of methods
}

您还可以使用扩展方法来获得类似的好处,例如

    public static bool Intersects<T>(this IRange<T> self, IRange<T> other) where T : IComparable<T>

    public static bool Intersects<T>(this IRange<T> self, IRange<T> other, IComparer<T> comparer) 

【讨论】:

  • 谢谢@JonasH。您能否也给我您的Range 类型,因为我从未使用过类似的东西?
  • 我包含 Range 结构并且确实发生了多个错误:Range&lt;T&gt; doesn't implement IEquatable&lt;Range&lt;T&gt;&gt;.Equals&lt;Range&lt;T&gt;&gt;Union&lt;T&gt;: Range&lt;T&gt; doesn't include a constructor that accepts two argumentsMerge&lt;T&gt;: Range&lt;T&gt; doesn't include a definition for Intersects and UnionGaps&lt;T&gt;: KeyComparer&lt;Range&lt;T&gt;&gt; not found and Range&lt;T&gt; doesn't include a constructor that accepts two arguments
  • 我已经修复了示例中的一些问题,但我仍然希望您根据自己的情况调整代码,而不是逐字复制。
猜你喜欢
  • 2023-04-10
  • 1970-01-01
  • 2015-02-07
  • 1970-01-01
  • 2021-12-11
  • 1970-01-01
  • 2020-05-28
  • 2015-03-28
  • 2019-01-18
相关资源
最近更新 更多