【问题标题】:Merging overlapping time intervals?合并重叠的时间间隔?
【发布时间】:2012-07-14 00:44:06
【问题描述】:

我有以下:

public class Interval
{
   DateTime Start;
   DateTime End; 
}

我有一个包含多个间隔的List<Interval> 对象。我正在努力实现以下目标(我使用数字使其易于理解):

[(1, 5), (2, 4), (3, 6)] --->  [(1,6)]
[(1, 3), (2, 4), (5, 8)] --->  [(1, 4), (5,8)]

我目前在 Python 中这样做如下:

def merge(times):
    saved = list(times[0])
    for st, en in sorted([sorted(t) for t in times]):
        if st <= saved[1]:
            saved[1] = max(saved[1], en)
        else:
            yield tuple(saved)
            saved[0] = st
            saved[1] = en
    yield tuple(saved)

但我试图在 C# 中实现相同的效果(LINQ 是最好的,但可选)。有关如何有效地做到这一点的任何建议?

【问题讨论】:

  • 对于给定的Interval,您是否确保(Start
  • @AndreCalil:是的。我可以保证那个条件。
  • 间隔是否总是在原始列表中排序?
  • 不。但我想我可以在List 上使用OrderBy 来实现这一点。

标签: c# linq


【解决方案1】:

这是一个使用 yield return 的版本 - 我发现它比使用 Aggregate 查询更容易阅读,尽管它仍然是懒惰的评估。这假设您已经订购了列表,如果没有,只需添加该步骤。

IEnumerable<Interval> MergeOverlappingIntervals(IEnumerable<Interval> intervals)
{
  var accumulator = intervals.First();  
  intervals = intervals.Skip(1);

  foreach(var interval in intervals)
  {
    if ( interval.Start <= accumulator.End )
    {
        accumulator = Combine(accumulator, interval);
    }
    else
    {
        yield return accumulator;
        accumulator = interval;     
    }       
  }

  yield return accumulator;
}

Interval  Combine(Interval start, Interval end)
{
  return new Interval 
  {
    Start = start.Start,
    End = Max(start.End, end.End),
  };
}

private static DateTime Max(DateTime left, DateTime right) 
{
    return (left > right) ? left : right;
}

【讨论】:

  • 我认为这个解决方案是不正确的。组合时,您应该采用更大的 End of interval 和 accumulator。
  • 我不确定你的意思。您能否举一个产生错误答案的示例?
  • 啊,我明白了。 [(1, 5), (2, 4)] 返回 [(1,4)] - 这显然是错误的。如果您想在修复程序中进行编辑,我可以接受,否则我稍后会处理。
  • 崩溃次数 = 0?
  • 我认为 [(1,12), (15,31), (10,22)] 将返回 [(1,12),(10,31)] 而不是 [(1, 31)]。我认为您需要先订购集合。
【解决方案2】:

今晚我被“不是在这里创造”综合症所困扰,所以这是我的。使用枚举器直接为我节省了几行代码,使其更清晰(IMO),并处理了没有记录的案例。我想如果你关心它,它也可能会跑得更快......

public IEnumerable<Tuple<DateTime, DateTime>> Merge(IEnumerable<Tuple<DateTime, DateTime>> ranges)
{
    DateTime extentStart, extentEnd;
    using (var enumerator = ranges.OrderBy(r => r.Item1).GetEnumerator()) {
        bool recordsRemain = enumerator.MoveNext();
        while (recordsRemain)
        {
            extentStart = enumerator.Current.Item1;
            extentEnd = enumerator.Current.Item2;
            while ((recordsRemain = enumerator.MoveNext()) && enumerator.Current.Item1 < extentEnd)
            {
                if (enumerator.Current.Item2 > extentEnd)
                {
                    extentEnd = enumerator.Current.Item2;
                }
            }
            yield return Tuple.Create(extentStart, extentEnd);
        }
    }
}

在我自己的实现中,我使用TimeRange 类型来存储每个Tuple&lt;DateTime, DateTime&gt;,就像这里的其他人一样。我没有将其包含在此处只是为了保持专注/关注主题。

【讨论】:

    【解决方案3】:

    这可能不是最漂亮的解决方案,但它也可以工作

    public static List<Interval> Merge(List<Interval> intervals)
    {
        var mergedIntervals = new List<Interval>();
        var orderedIntervals = intervals.OrderBy<Interval, DateTime>(x => x.Start).ToList<Interval>();
    
        DateTime start = orderedIntervals.First().Start;
        DateTime end = orderedIntervals.First().End;
    
        Interval currentInterval;
        for (int i = 1; i < orderedIntervals.Count; i++)
        {
            currentInterval = orderedIntervals[i];
    
            if (currentInterval.Start < end)
            {
                end = currentInterval.End;
            }
            else
            {
                mergedIntervals.Add(new Interval()
                {
                    Start = start,
                    End = end
                });
    
                start = currentInterval.Start;
                end = currentInterval.End;
            }
        }
    
        mergedIntervals.Add(new Interval()
                    {
                        Start = start,
                        End = end
                    });
    
        return mergedIntervals;
    }
    

    我们将不胜感激。

    问候

    【讨论】:

    • 这是一个很好的总体思路。不过,我注意到一个错误。它不会返回最后一个合并的区间。
    • 我找不到任何这种方法不起作用的情况。
    • 我知道这是旧代码,但如果有人偶然发现它并优先使用它而不是 IEnumerable 变体,则应阅读以下行(否则它将折叠包含较短的合并间隔): if (currentInterval.Start currentInterval.End ? end : currentInterval.End); }
    【解决方案4】:

    这种合并通常被视为函数式语言中的折叠。 LINQ 等效项是Aggregate

    IEnumerable<Interval<T>> Merge<T>(IEnumerable<Interval<T>> intervals) 
      where T : IComparable<T>
    {
        //error check parameters
        var ret = new List<Interval<T>>(intervals);
        int lastCount
        do
        {
            lastCount = ret.Count;
            ret = ret.Aggregate(new List<Interval<T>>(),
                        (agg, cur) =>
                        {
                            for (int i = 0; i < agg.Count; i++)
                            {
                                var a = agg[i];
                                if (a.Contains(cur.Start))
                                {
                                    if (a.End.CompareTo(cur.End) <= 0)
                                    {
                                        agg[i] = new Interval<T>(a.Start, cur.End);
                                    }
                                    return agg;
                                }
                                else if (a.Contains(cur.End))
                                {
                                    if (a.Start.CompareTo(cur.Start) >= 0)
                                    {
                                        agg[i] = new Interval<T>(cur.Start, a.End);
                                    }
                                    return agg;
                                }
                            }
                            agg.Add(cur);
                            return agg;
                        });
        } while (ret.Count != lastCount);
        return ret;
    }
    

    我将 Interval 类设为通用 (Interval&lt;T&gt; where T : IComparable&lt;T&gt;),添加了一个 bool Contains(T value) 方法,并使其不可变,但如果您想像现在一样使用类定义,则不需要对其进行太多更改。

    【讨论】:

      【解决方案5】:

      我使用 TimeRange 作为存储范围的容器:

      public class TimeRange
      {
          public TimeRange(DateTime s, DateTime e) { start = s;  end = e; }
      
          public DateTime start;
          public DateTime end;
      }
      

      它将问题划分为两个时间范围的组合。因此,当前时间范围(工作)与之前合并的时间范围相匹配。如果先前添加的时间范围之一已过时,则将其删除并使用新的时间范围(结合工作和匹配的时间范围)。 我想出的两个范围()和[]的情况如下:

      1. [] ()
      2. ([])
      3. [(])
      4. [()]
      5. ([)]
      6. ()[]

        public static IEnumerable<TimeRange> Merge(IEnumerable<TimeRange> timeRanges)
        {
            List<TimeRange> mergedData = new List<TimeRange>();
        
            foreach (var work in timeRanges)
            {
                Debug.Assert(work.start <= work.end, "start date has to be smaller or equal to end date to be a valid TimeRange");
                var tr = new TimeRange(work.start, work.end);
        
                int idx = -1;
                for (int i = 0; i < mergedData.Count; i++)
                {
                    if (tr.start < mergedData[i].start)
                    {
                        if (tr.end < mergedData[i].start)
                            continue;
                        if (tr.end < mergedData[i].end)
                            tr.end = mergedData[i].end;
                    }
                    else if (tr.start < mergedData[i].end)
                    {
                        tr.start = mergedData[i].start;
        
                        if (tr.end < mergedData[i].end)
                            tr.end = mergedData[i].end;
                    }
                    else
                        continue;
        
                    idx = i;
                    mergedData.RemoveAt(i);
                    i--;
                }
        
                if (idx < 0)
                    idx = mergedData.Count;
        
                mergedData.Insert(idx, tr);
            }
        
            return mergedData;
        }
        

      【讨论】:

        猜你喜欢
        • 2019-04-12
        • 2014-02-23
        • 2013-10-16
        • 1970-01-01
        • 2016-01-24
        • 2011-02-03
        • 1970-01-01
        • 2021-09-21
        相关资源
        最近更新 更多