文章目录

  • 1. 时间段重叠校验问题
  • 2. 解决方案


1. 时间段重叠校验问题

项目开发过程中经常碰到需要校验一连串时间段是否有重叠的问题,通常直观的解决方式是采用两层循环,从头至尾依次将集合中的时间段元素两两比较即可得出结果。这种方式虽然可行,但从实现上来看着实算不上优雅,只是能 work 而已。笔者花了一些时间分析,发现要校验时间段是否重叠其实只需要一点逆向思维,即只要列出两个时间段不重叠的情况,那么剩下的所有情形中二者必定有重叠。基于此,以下用两个时间段 segment1segment2 为例子进行分析:

时间段对象都有 startend 属性来标定一个时间范围,而时间是一维线性的,那么两个时间段的重叠与否其实只有以下 3 种情况:

  1. segment1.end < segment2.start ,不重叠
  2. segment1.start > segment2.end ,不重叠
  3. 其他情况,必定有重叠
/*
不重叠情况1:
                 segment1                    segment2
                /         \                 /         \
*    ----------|------------|--------------|-----------|-----
         start:2         end:5        start:9         end:15
*
*
不重叠情况2:
                 segment2                     segment1
                /         \                 /         \
*    ----------|------------|--------------|-----------|-----
         start:9         end:15        start:17        end:23
*
*/

2. 解决方案

经过上一节分析,3 种情况在笔者脑海中想到的处理方式是排序,于是最终有了以下方案:

  1. 时间段对象重写 Comparable#compareTo() 接口方法,在这个方法中定义比较规则,segment1 整体在 segment2 左侧返回 -1,segment1 整体在 segment2 右侧返回 1,其他情况返回 0
  2. 在时间段对象中添加重叠计数器 overlapCounter,当重写方法 Comparable#compareTo() 返回 0 时,计数器自增,记录当前时间段对象在与其他时间段对象比较排序过程中出现重叠的次数
  3. 使用 Java 8 的 Stream API 处理时间段集合,首先指定时间段对象重写的 Comparable#compareTo()Comparator 比较器实现,调用 Stream#sorted() 方法对时间段集合进行排序
  4. 经过排序操作,时间段对象的重叠计数器 overlapCounter 会将当前对象与集合中其他时间段对象的重叠计算结果记录下来,只要将集合中各个时间段对象的计数器累加,结果大于 0 即集合内有时间段出现重叠
  • 以上方案的关键在于采用 JDK 的排序比较,虽然其底层实现大概率也会有多层循环,但是一来 JDK 中的类都经过严格测试,通常较为可靠;二来使用 JDK 已有的 API, 也让代码更为简洁优雅
  • 除了使用 Integer 计数器记录重叠次数,其实也可以用集合来记录更多信息,比如与当前时间段对象重叠的其他时间段对象信息,这些可用于提示到底哪些时间段出现了重叠。总之,方案已经就绪,效果如何就看读者的想象力了
public class TimeSegment implements Comparable {

    private Integer start;

    private Integer end;

    private Integer overlapCounter = 0;

    public TimeSegment(Integer start, Integer end) {
        this.start = start;
        this.end = end;
    }

    public Integer getOverlapCounter() {
        return overlapCounter;
    }

    @Override
    public int compareTo(Object o) {
        TimeSegment other = (TimeSegment) o;
        if (this.end < other.start) {
            return -1;
        } else if (this.start > other.end) {
            return 1;
        }
        overlapCounter++;
        return 0;
    }

    public static void main(String[] args) {
        List<TimeSegment> list = new ArrayList<>();
        list.add(new TimeSegment(6, 9));
        list.add(new TimeSegment(1, 4));
        list.add(new TimeSegment(3, 5));

        long count = list.stream()
                .sorted(TimeSegment::compareTo)
                .mapToLong(TimeSegment::getOverlapCounter)
                .sum();
        System.out.println(count > 0);
    }
}