从间隔列表中查找所有间隔集,其中一组中的每个间隔与该集中的所有间隔重叠

时间:2015-03-11 20:42:23

标签: python algorithm intervals dateinterval

不是查询具有开始日期和结束日期的间隔列表,而是从列表中检索仅与搜索开始日期和结束日期重叠的所有间隔,最佳方法是什么:

From a list of date intervals, 
Find all unique sets of intervals
Where every interval in each set overlaps with each other interval in that set

使用整数示例,获取整数间隔[{1,3},{2,4},{4,5},{5,7},{6,8}]的列表。从此列表中,以下是所有唯一的间隔集,其中每个集合中的每个间隔都相互重叠

{ {1,3}, {2,4} }
{ {2,4}, {4,5} }
{ {4,5}, {5,7} }
{ {5,7}, {6,8} }

这是DateInterval的类:

from datetime import datetime
class DateInterval(object):
    def __init__(self, start_time, end_time):
        self.start_time = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
        seld.end_time = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')

    ''' eq, gt, hash methods removed for clarity '''

我会收到一个按start_time升序排序的间隔列表,如下所示:

intervals = [DateInterval(start_time='2015-01-01 08:00:00', end_time='2015-01-01 08:30:00'),
             DateInterval(start_time='2015-01-01 08:00:00', end_time='2015-01-01 10:00:00'),
             DateInterval(start_time='2015-01-01 09:00:00', end_time='2015-01-01 11:00:00'),
             DateInterval(start_time='2015-01-01 10:00:00', end_time='2015-01-01 12:00:00'),
             DateInterval(start_time='2015-01-01 13:00:00', end_time='2015-01-01 16:00:00'),
             DateInterval(start_time='2015-01-01 14:00:00', end_time='2015-01-01 17:00:00'),
             DateInterval(start_time='2015-01-01 15:00:00', end_time='2015-01-01 18:00:00'),
             DateInterval(start_time='2015-01-01 20:00:00', end_time='2015-01-01 22:00:00'),
             DateInterval(start_time='2015-01-01 20:00:00', end_time='2015-01-01 22:00:00')
             ]

(在此示例列表中,开始日期和结束日期始终均匀地在一小时内着陆。但是,它们可以在任何一秒上降落(或者可能是几毫秒)。在有关重叠间隔的stackoverflow上搜索详尽的问题列表后,I found the Interval Tree to be unsuitable for Date Intervals)。

我轻微优化的强力方法包含三个任务

  1. 确定所有非唯一集合的时间间隔,其中每个集合中的至少一个时间间隔与该集合中的所有其他时间间隔重叠
  2. 对步骤1的结果进行重复数据删除,以找到所有唯一集的时间间隔,其中每个集合中的至少一个时间间隔与该集合中的所有其他时间间隔重叠
  3. 从1的结果中,只找到一组中每个区间与该组中所有其他区间重叠的那些组
  4. 1。

    以下查找所有非唯一集合,其中每个集合中只有一个间隔与该集合中的每个其他间隔重叠,方法是将间隔列表中的每个间隔与所有其他间隔进行天真地比较。它假设间隔列表按日期时间升序排序,从而启用break优化

    def search(intervals, start_date, end_date):
        results = []
        for interval in intervals:
            if end_date >= interval.start_time:
                if start_date <= interval.end_time:
                    results.append(interval)
            else:
                break # This assumes intervals are sorted by date time ascending
    
    像这样使用

    search

    brute_overlaps = []
    for interval in intervals:
        brute_overlaps.append(search(intervals, interval.start_time, interval.end_time))
    

    2

    以下重复删除集合列表:

    def uniq(l):
        last = object()
        for item in l:
            if item == last:
                continue
            yield item
            last = item
    
    def sort_and_deduplicate(l):
        return list(uniq(sorted(l, reverse=True)))
    

    第3

    以下内容通过将集合中的每个间隔与该集合中的每个其他间隔进行天真地比较,找到每个集合中每个间隔与该集合中所有其他间隔重叠的所有集合:

    def all_overlap(overlaps):
        results = []
        for overlap in overlaps:
            is_overlap = True
            for interval in overlap:
                for other_interval in [o for o in overlap if o != interval]:
                    if not (interval.end_time >= other_interval.start_time and interval.start_time <= other_interval.end_time):
                        is_overlap = False
                        break # If one interval fails
                 else:        # break out of
                     continue # both inner for loops
                 break        # and try next overlap
    
            if is_overlap: # all intervals in this overlap set overlap with each other
                results.append(overlap)
        return results
    

1 个答案:

答案 0 :(得分:0)

一组间隔,其中每个间隔必须与集合中的每个间隔重叠,将具有它们全部重叠的共同点。相反,查询某个点的所有间隔将为您提供一组所有相互重叠的间隔。

考虑到这一点,你的问题会缩小为“我可以通过更改我要查询的点来获得的间隔的不同子集是什么?”。获取所有这些不同子集的简单方法是找到重叠间隔必须更改的事件的位置,并在每对事件之间的某个点进行查询。

在间隔的情况下,事件对应于任何间隔开始或任何间隔结束。因此,您只需扫描从左到右开始和停止的间隔,同时跟踪已启动但未结束的那些间隔。这为您提供了所有最大的相互重叠的子集。

在伪代码中......

maximalMutuallyOverlappingSubsets =
    intervals
    .flatMap(e => [(e.start, e, true),
                   (e.end, e, false)])
    .sortedBy(e => e[0]).
    .scan({}, (prevSet, (x, interval, add)) =>
        if add
        then prevSet + {interval}
        else prevSet - {interval})
    .distinct() - {{}}

O(n lg n)时间内运行,排序是最昂贵的一步。

如果您不熟悉,flatMap会将列表中的每个项目投影到生成的集合中,然后将所有生成的集合项目连接在一起。 Scan从给定的累加器开始,并使用给定的函数将下一个项目组合到累加器中,同时产生中间结果。