获取与另一个区间不相交的区间的算法

问题描述 投票:0回答:1

假设我有一个很大的间隔

LargeInterval interval = new LargeInterval
{
   StartPeriod = DateTime.Today,
   EndPeriod = DateTime.Today.AddYears(1),
};

哪里

public class LargeInterval 
{
 public DateTime StartPeriod { get; set; }
 public DateTime EndPeriod { get; set; }
}

还有一些“较小”的间隔

public class SmallInterval
{
    public DateTime From{ get; set; }
    public DateTime To { get; set; }
}

例如,

SmallInterval s1 = new SmallInterval
{
 From = "2024-05-01"
 End = "2024-07-03"
}

SmallInterval s2 = new SmallInterval
{
 From = "2024-08-01"
 End = "2024-10-03"
}

我确实想得到每个

SmallInterval
interval
类型的
LargeInterval
之间不相交的部分。比如:

DateTime.Today until "2024-04-30"

"2024-07-04" until "2024-07-31"

"2024-10-04" until DateTime.Today.AddYears(1).AddDays(-1)

我尝试过这样的事情:

    DateTime startPeriod = interval.StartPeriod;
    DateTime endPeriod = interval.EndPeriod;

    IEnumerable<SmallIntervals> existingPeriodsIntersectingTheNewPeriod = smallIntervals.Where(x => x.From <= endPeriod && x.To >= startPeriod).OrderBy(x => x.From);

foreach (var stsp in existingPeriodsIntersectingTheNewPeriod)
{

/// handling all scenarios
}
c# .net algorithm datetime
1个回答
0
投票

我有一个适用于

double
范围的解决方案。您可以将其与
DateTime
值一起使用,方法是使用
DateTime.ToOADate()
DateTime.FromOADate()
double
DateTime
之间进行转换。

用法如下:

var rangeExcluder = new RangeExcluder();

// Include large interval.
rangeExcluder.Include(
    DateTime.Today.ToOADate(),
    DateTime.Today.AddYears(1).ToOADate());

// Exclude small interval #1.
rangeExcluder.Exclude(
    new DateTime(2024, 05, 01).ToOADate(),
    new DateTime(2024, 07, 03).AddDays(1).ToOADate());

// Exclude small interval #2.
rangeExcluder.Exclude(
    new DateTime(2024, 08, 01).ToOADate(),
    new DateTime(2024, 10, 03).AddDays(1).ToOADate());

foreach (var region in rangeExcluder.IncludedRanges())
{
    Console.WriteLine($"{DateTime.FromOADate(region.Start):d} - {DateTime.FromOADate(region.End).AddTicks(-1):d}");
}

请注意,传递给

Include()
Exclude()
并通过
IncludedRanges()
返回的范围都是半开区间,包括开始但不包括结束。这就是为什么我在设置范围时使用
AddDays(1)
,在输出结果时使用
.AddTicks(-1)

上面的代码将输出:

12/03/2024 - 30/04/2024
04/07/2024 - 31/07/2024
04/10/2024 - 11/03/2025

RangeExcluder
的实现如下所示。请注意,它处理多个重叠区域,因此您可以包含与其他包含区域重叠的区域,并排除与其他排除区域重叠的区域。

public sealed class RangeExcluder
{
    /// <summary>Adds a region to be included. <paramref name="start"/> must be less than or equal to <paramref name="end"/>.</summary>
    /// <param name="start">The start of the region to be included. Must be less than or equal to <paramref name="end"/>.</param>
    /// <param name="end">The exclusive end of the region to be included. Must be greater than or equal to <paramref name="start"/>.</param>
    public void Include(double start, double end)
    {
        _boundaries.Add(new Boundary(start, isStart: true,  isIncluded: true));
        _boundaries.Add(new Boundary(end,   isStart: false, isIncluded: true));

        _sorted = false;
    }

    /// <summary>Adds a region to be excluded. <paramref name="start"/> must be less than or equal to <paramref name="end"/>.</summary>
    /// <param name="start">The start of the region to be excluded. Must be less than or equal to <paramref name="end"/>.</param>
    /// <param name="end">The exclusive end of the region to be excluded. Must be greater than or equal to <paramref name="start"/>.</param>
    public void Exclude(double start, double end)
    {
        _boundaries.Add(new Boundary(start, isStart: true,  isIncluded: false));
        _boundaries.Add(new Boundary(end,   isStart: false, isIncluded: false));

        _sorted = false;
    }

    /// <summary>
    /// Iterates all the included ranges after removing all the excluded ranges from them. <br/>
    /// The start values are INCLUSIVE and the end values are EXCLUSIVE.
    /// This may be an O(N.LogN) operation, so call it sparingly.
    /// </summary>
    public IEnumerable<(double Start, double End)> IncludedRanges()
    {
        sortIfNecessary();

        return includedRanges();
    }

    void sortIfNecessary()
    {
        if (_sorted)
            return;

        _boundaries.Sort();
        _sorted = true;
    }

    IEnumerable<(double start, double end)> includedRanges()
    {
        int included = 0;
        int excluded = 0;
        double start = 0;

        for (int i = 0; i < _boundaries.Count; ++i)
        {
            if (_boundaries[i].IsStart) // Starting a region...
            {
                if (_boundaries[i].IsIncluded)  // Starting an included region...
                {
                    if (++included == 1 && excluded == 0)           // Starting a new included region,
                        start = _boundaries[i].Time;                // so remember its start time.
                }
                else                            // Starting an excluded region...
                {
                    if (++excluded == 1 && included > 0)            // Ending an included region,
                        yield return (start, _boundaries[i].Time);  // so return its range.
                }
            }
            else                        // Ending a region...
            {
                if (_boundaries[i].IsIncluded)  // Ending an included region...
                {
                    if (--included == 0 && excluded == 0)           // Ending an included region,
                        yield return (start, _boundaries[i].Time);  // so return its range.
                }
                else                            // Ending an excluded region...
                {
                    if (--excluded == 0 && included > 0)            // Starting an included region,
                        start = _boundaries[i].Time;                // so remember its start time.
                }
            }
        }
    }

    readonly List<Boundary> _boundaries = [];

    bool _sorted;

    // Struct to hold starts and ends of included and excluded regions.
    // (Note: A struct is much more performant than a class for the specific way it is used here.)

    readonly struct Boundary : IComparable<Boundary>
    {
        public Boundary(double time, bool isStart, bool isIncluded)
        {
            Time = time;
            IsStart = isStart;
            IsIncluded = isIncluded;
        }

        // Order by [Time, IsStart] so if two times are the same, a start time is before an end time.

        public int CompareTo(Boundary other)
        {
            if (this.Time < other.Time)
                return -1;

            if (this.Time > other.Time)
                return 1;

            if (this.IsStart == other.IsStart)
                return 0;

            return this.IsStart ? -1 : 1;
        }

        public readonly double Time;
        public readonly bool IsStart;
        public readonly bool IsIncluded;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.