c#两个范围周期列表的差异

时间:2016-02-25 16:34:03

标签: c# algorithm date date-range difference

你能帮助我解决两个范围周期之间存在差异的算法。

例如:

第一组范围期: {[01.01.2015 - 10.01.2015],[2015年1月15日 - 2015年1月30日]}

第二组范围期: {[02.01.2015-20.01.2015],[2015年1月25日 - 2015年1月25日]}

结果应为: {[01.01.2015 - 01.01.2015],[2015年1月21日 - 2015年1月24日],[2015年1月26日 - 2015年1月30日]}

到目前为止,这是我的代码:

        private object GetDifferenceRangePeriods(List<RangePeriod> all, List<RangePeriod> toRemove)
    {
        foreach (var rp in toRemove)
        {
            var overlappedItems = all.Where(c => DateHelper.IsOverlapping(c.StartDate, c.EndDate, rp.StartDate, rp.EndDate)).ToList();
            foreach (var itm in overlappedItems)
            {
                if (itm.StartDate == rp.StartDate)
                {
                    if (itm.EndDate <= rp.EndDate)
                    {
                        all.Remove(itm);
                    }
                    else if(itm.EndDate > rp.EndDate)
                    {
                        itm.StartDate = rp.EndDate.AddDays(+1);
                    }
                }

                if (itm.StartDate < rp.StartDate)
                {
                    if (itm.EndDate <= rp.EndDate)
                    {
                        itm.EndDate = rp.StartDate.AddDays(-1);
                    }
                    if (itm.EndDate > rp.EndDate)
                    {
                        itm.EndDate= rp.StartDate.AddDays(-1);

                        var newRangePeriod = new RangePeriod
                        {
                            StartDate = rp.EndDate.AddDays(+1),
                            EndDate = itm.EndDate
                        };
                        all.Add(newRangePeriod);
                    }
                }

                if (itm.StartDate > rp.StartDate)
                {
                    if (itm.EndDate <= rp.EndDate)
                    {
                        all.Remove(itm);
                    }
                    if (itm.EndDate > rp.EndDate)
                    {
                        itm.StartDate = itm.EndDate.AddDays(+1);
                    }
                }
            }
        }

        return all;
    }

1 个答案:

答案 0 :(得分:3)

就个人而言,我采用延迟初始化方法来做到这一点。

public static IEnumerable<RangePeriod> Minus(
    this IEnumerable<RangePeriod> first,
    IEnumerable<RangePeriod> second)
{
    // First make sure that the lists are ordered by their start dates.
    var firstSorted = first.OrderBy(r => r.StartDate);
    var secondSorted = second.OrderBy(r => r.StartDate);

    // get the enumerators of the sorted sequences.
    using (var keep = firstSorted.GetEnumerator())
    using (var remove = secondSorted.GetEnumerator())
    {
        // if there are no ranges to keep then return an empy sequence.
        if (!keep.MoveNext()) yield break;
        var currentKeep = keep.Current;
        RangePeriod currentRemove = null;
        if (remove.MoveNext()) currentRemove = remove.Current;
        while (true)
        {
            // if there are no more remove ranges or the remove range is after the keep
            // then just yield the keep range and move to the next keep range.
            if (currentRemove == null || currentKeep.EndDate < currentRemove.StartDate)
            {
                yield return currentKeep;
                if (!keep.MoveNext()) yield break;
                currentKeep = keep.Current;
                continue;
            }

            // if the remove range is before the keep then move to the next remove range.
            if (currentRemove.EndDate < currentKeep.StartDate)
            {
                currentRemove = remove.MoveNext() ? remove.Current : null;
                continue;
            }

            // if the remove range ends before the keep range
            if (currentRemove.EndDate < currentKeep.EndDate)
            {
                // if the keep starts before the remove then we yield a range from the keep's start
                // to the remove's start - 1 day.
                if (currentKeep.StartDate < currentRemove.StartDate)
                {
                    yield return new RangePeriod(currentKeep.StartDate, currentRemove.StartDate.AddDays(-1));
                }

                // change the keep's start to the remove's end + 1 and move to the next remove range.
                currentKeep = new RangePeriod(currentRemove.EndDate.AddDays(1), currentKeep.EndDate);
                currentRemove = remove.MoveNext() ? remove.Current : null;
                continue;
            }

            // if the remove range completely covers the keep then move to the next keep (if there is one)
            if (currentRemove.StartDate < currentKeep.StartDate)
            {
                if (!keep.MoveNext()) yield break;
                currentKeep = keep.Current;
                continue;
            }

            // Otherwise the remove range starts after the keep starts but before the keep ends and the
            // remove ends after the keep ends, so we need to yield a range that starts on the keep's
            // start and ends before the remove's start and move to the next keep range.
            yield return new RangePeriod(currentKeep.StartDate, currentRemove.StartDate.AddDays(-1));
            if (!keep.MoveNext()) yield break;
            currentKeep = keep.Current;
        }
    }
}

这将允许您执行以下操作

var first = new[]
            {
                new RangePeriod(new DateTime(2015, 1, 1), new DateTime(2015, 1, 10)),
                new RangePeriod(new DateTime(2015, 1, 15), new DateTime(2015, 1, 30))
            };
var second = new[]
            {
                new RangePeriod(new DateTime(2015, 1, 2), new DateTime(2015, 1, 20)),
                new RangePeriod(new DateTime(2015, 1, 25), new DateTime(2015, 1, 25))
            };

foreach(var range in first.Minus(second)) 
    Console.WriteLine($"{range.StartDate} to {range.EndDate}");

获得这些结果

  

1/1/2015 12:00:00 AM至1/1/2015 12:00:00 AM

     

1/21/2015 12:00:00 AM至1/24/2015 12:00:00 AM

     

1/26/2015 12:00:00 AM至1/30/2015 12:00:00 AM