在Java中相交重叠的间隔

时间:2013-07-09 08:06:02

标签: java intersection intervals

我有一组可能重叠的日期范围。我想创建具有调整日期的新日期范围,而不是组合这些重叠的日期范围,例如:

|---------------------–|
        |-----| 
            |--------------–|

最终应该是:

|-------|---|-|--------|----|

有没有一种有效的方法可以用Java解决这个问题?

提前致谢!

更新 我在第一个问题中没有提到我自己的方法,所以这里是:我只需要一个间隔的开始和结束日期,然后将它添加到一个有序集合中。之后我会迭代该集合并根据重新排序的日期创建新的间隔。

5 个答案:

答案 0 :(得分:3)

您可以使用Guava's Range支持。没有将它与Date对象一起使用但它可以工作。 结合RangeSet,您可以添加所有日期范围,然后检查日期是否在范围内,获得完整范围等。

答案 1 :(得分:2)

要解决此类问题,请使用开始日期作为第一个条件并将结束日期作为秒来对间隔进行排序。这样,您可以在单次迭代中交叉间隔。如果您的间隔与另一个不久开始的间隔重叠,那么它在排序顺序中的后继应该是重叠间隔,依此类推。

答案 2 :(得分:0)

基本理念:

  • 将每个间隔分成开始和结束点
  • 对点进行排序
  • 遍历各点并在所有相邻点之间创建新的间隔 跟踪startIntervals - endIntervals,只要此数字为0,该范围内就不应有间隔。

答案 3 :(得分:0)

使用java.time

让我们使用现代的java.time类。

LocalDate类表示没有时间且没有时区的仅限日期的值。

创建一个小班DateRange来保持停止和开始日期。

public class DateRange {
    public LocalDate start , stop ;

    public DateRange ( LocalDate start , LocalDate stop ) {
        if(stop.isBefore(start)){
            throw new IllegalArgumentException ("The stop date is before the start date." );
        }
        this.start = start;
        this.stop = stop;
    }

    @Override
    public String toString () {
        return "DateRange{ " + "start=" + start + ", stop=" + stop + " }";
    }

}

实例化该类的对象,并收集。

    List<DateRange> ranges = new ArrayList<> ( 3 );
    ranges.add ( new DateRange ( LocalDate.of ( 2017 , Month.JANUARY , 17 ) , LocalDate.of ( 2017 , Month.MARCH , 7 ) ) );
    ranges.add ( new DateRange ( LocalDate.of ( 2017 , Month.FEBRUARY , 12 ) , LocalDate.of ( 2017 , Month.FEBRUARY , 16 ) ) );
    ranges.add ( new DateRange ( LocalDate.of ( 2017 , Month.FEBRUARY , 14 ) , LocalDate.of ( 2017 , Month.MARCH , 25 ) ) );

    System.out.println ( "ranges: " + ranges );

提取每个开始和每个停止,收集到List

    // Intersect and combine to create a sequence of new DateRange objects.
    // Collect each start & stop as individual `LocalDate` objects.
    List<LocalDate> dates = new ArrayList<> ( ranges.size () * 2 );
    for ( DateRange range : ranges ) {
        dates.add ( range.start );
        dates.add ( range.stop );
    }

对日期排序。如问题所示,各种范围可以彼此重叠。我们按时间顺序需要它们,以便建立新的邻接日期范围。

    // Sort the collection of dates.
    Collections.sort ( dates );

循环排序日期,将每个日期作为开始日期,将以下日期作为停止日期。

    // Loop the sorted dates, creating DateRange objects as we go.
    List<DateRange> rangesOutput = new ArrayList<> ( dates.size () ); // Not an exact initial capacity but good enough.
    for ( int i = 1 ; i < dates.size () ; i ++ ) {
        LocalDate start = dates.get ( i - 1 ); // Subtract one for silly index counting.
        LocalDate stop = dates.get ( i + 1 - 1 ); // Subtract one for silly index counting. Or use ( i ) instead.
        if (  ! start.equals ( stop ) ) {  // If not equal, proceed. (If equal, ignore and move on to next loop.)
            DateRange range = new DateRange ( start , stop );
            rangesOutput.add ( range );
        }
    }
    System.out.println ( "rangesOutput: " + rangesOutput );

跑步时。

  

范围:[DateRange {start = 2017-01-17,stop = 2017-03-07},DateRange {start = 2017-02-12,stop = 2017-02-16},DateRange {start = 2017- 02-14,停止= 2017-03-25}]

     

rangesOutput:[DateRange {start = 2017-01-17,stop = 2017-02-12},DateRange {start = 2017-02-12,stop = 2017-02-14},DateRange {start = 2017- 02-14,stop = 2017-02-16},DateRange {start = 2017-02-16,stop = 2017-03-07},DateRange {start = 2017-03-07,stop = 2017-03-25} ]

请参阅此code live at IdeOne.com

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和&amp; SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore

答案 4 :(得分:0)

使用我的时间库Time4J,可以采用方便的解决方案,而无需对真正的实施进行过多的头脑风暴:

// create the intervals
SimpleInterval<Date> i1 = SimpleInterval.between(new Date(0L), new Date(5000L));
SimpleInterval<Date> i2 = SimpleInterval.between(new Date(0L), new Date(7000L));
SimpleInterval<Date> i3 = SimpleInterval.between(new Date(1000L), new Date(2000L));

// collect the intervals
IntervalCollection<Date> icoll =
    IntervalCollection.onTraditionalTimeLine().plus(i3).plus(i2).plus(i1);

// split and iterate
for (ChronoInterval<Date> interval : icoll.withSplits().getIntervals()) {
    System.out.println(interval);
}

输出:

[Thu Jan 01 01:00:00 CET 1970/Thu Jan 01 01:00:01 CET 1970)
[Thu Jan 01 01:00:01 CET 1970/Thu Jan 01 01:00:02 CET 1970)
[Thu Jan 01 01:00:02 CET 1970/Thu Jan 01 01:00:05 CET 1970)
[Thu Jan 01 01:00:05 CET 1970/Thu Jan 01 01:00:07 CET 1970)

定义和收集所有区间后的主要调整只是在区间集合上调用withSplits()

另一个优点是可以稍微调整代码,以便与其他类型(如Java-8中的类java.time.Instant或内置的Time4J类型,如Moment,{{1})一起使用},PlainDate等也很容易实现。