如何解决检查日期是否重叠并返回最大计数
我有多个开始和结束的日期。 这些日期可能如下所示:
d1: |----------|
d2: |------|
d3: |--------------|
d4: |----|
d5: |----|
现在我需要检查重叠日期的最大计数。 所以在这个例子中,我们得到最多 3 个重叠日期(d1、d2、d3)。 考虑一下,可以有 0 到 n 个日期。
你能帮我完成这个任务吗? 提前致谢。
更新
输入:带有开始和结束点的 Java-Date 列表,例如 List,其中 MyCustomDate 包含开始和结束日期
输出: 重叠日期(作为 MyCustomDate 的列表)
每个时间跨度都包括一个带有小时和秒的 LocalDateTime 类型的开始和结束点。
解决方法
我的回答会考虑:
- 给定 (d3,d5) 不重叠 => 重叠 (d1,d3,d5) = 2,因为在给定时间只有两个日期会重叠。
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
class Event {
LocalDate startDate; // inclusive
LocalDate endDate; // inclusive
Event(LocalDate st,LocalDate end) {
this.startDate = st;
this.endDate = end;
}
// Getters & Setters omitted
}
public class Main {
public static void main(String[] args) {
List<Event> events = new ArrayList<Event>();
events.add(new Event(LocalDate.of(2019,1,1),LocalDate.of(2019,5,1))); // d1
events.add(new Event(LocalDate.of(2019,3,6,1))); // d2
events.add(new Event(LocalDate.of(2019,2,7,1))); // d3
events.add(new Event(LocalDate.of(2019,8,12,1))); // d4
// d5 do not overlap d3
events.add(new Event(LocalDate.of(2018,31))); // d5
Integer startDateOverlaps = events.stream().map(Event::getStartDate).mapToInt(date -> overlap(date,events)).max().orElse(0);
Integer endDateOverlaps = events.stream().map(Event::getEndDate).mapToInt(date -> overlap(date,events)).max().orElse(0);
System.out.println(Integer.max(startDateOverlaps,endDateOverlaps));
}
public static Integer overlap(LocalDate date,List<Event> events) {
return events.stream().mapToInt(event -> (! (date.isBefore(event.startDate) || date.isAfter(event.endDate))) ? 1 : 0).sum();
}
}
我们对每个重叠日期求和(即使是它本身,否则 (d1,d2,d3) 只会计算 (d2,d3) 以进行 d1 检查)并测试每个 startDate 和 endDate。
,您可以简单地为每个 startDate
(按一天的粒度)生成 endDate
和 Event
之间的所有事件并计算 Map
,其中键是 {{1} }(作为单独的一天),value 是该日期出现的次数:
LocalDate
,
此时存在的其他 2 个答案都是 O(n2),对所有事件进行笛卡尔连接。这个答案展示了一种具有 O(n log n) 时间复杂度的替代方法。
我们所做的是构建一个有序的日期列表,并为每个日期注册在该日期开始和结束的范围。它可以存储为单个数字,例如如果范围结束 (-1) 并且 3 个范围开始 (+3),则日期的增量为 +2。
基本上,每个事件实际上是 2 个事件,一个开始事件和一个结束事件。
然后我们按日期顺序迭代列表,更新运行总数,并记住最大运行总数。
有几种编码方法。我将使用常规循环,而不是流,并且由于问题说每个日期都有一个小到毫秒的开始和结束点,我们将使用一个带有两个 DateRange
字段的 Instant
对象。
static int maxRangeOverlaps(List<DateRange> ranges) {
Map<Instant,Delta> dateDelta = new TreeMap<>();
for (DateRange range : ranges) {
dateDelta.computeIfAbsent(range.getStart(),k -> new Delta()).value++;
dateDelta.computeIfAbsent(range.getEnd(),k -> new Delta()).value--;
}
int total = 0,max = 0;
for (Delta delta : dateDelta.values())
if ((total += delta.value) > max)
max = total;
return max;
}
public final class DateRange {
private final Instant start; // inclusive
private final Instant end; // exclusive
// Constructor and getter methods here
}
final class Delta {
public int value;
}
测试
// ....:....1....:....2....:....3
// d1: |----------|
// d2: |------|
// d3: |--------------|
// d4: |----|
// d5: |----|
List<DateRange> ranges = Arrays.asList(
new DateRange(LocalDate.of(2021,4),LocalDate.of(2021,15)),new DateRange(LocalDate.of(2021,10),17)),6),21)),23),28)),6)));
System.out.println(maxRangeOverlaps(ranges)); // prints 3
通过添加一个辅助构造函数,上述测试被简化为使用 LocalDate
而不是 Instant
:
public DateRange(LocalDate start,LocalDate end) {
this(start.atStartOfDay().toInstant(ZoneOffset.UTC),end.atStartOfDay().toInstant(ZoneOffset.UTC));
}
,
这个问题最初要求日期。发布答案后,问题更改为要求 LocalDateTime
。我会留下这个答案,因为它 (a) 回答了最初发布的问题,并且 (b) 可能对其他人有帮助。
其他答案看起来很有趣并且可能是正确的。但我发现以下代码更易于遵循和验证/调试。
警告:我并不声称此代码是最好的、最精简的或最快的。坦率地说,我在这里的尝试只是为了突破我对使用 Java 流和 lambda 的理解的极限。
不要发明自己的类来保存开始/结束日期。 ThreeTen-Extra 库提供了一个 LocalDateRange
类来将附加到时间线的时间跨度表示为一对 java.time.LocalDate
对象。 LocalDateRange
提供了多种方法:
- 比较,例如
abuts
和overlaps
。 - 工厂方法,例如
union
和intersection
。
我们可以使用 Java 9 及更高版本中方便的 List.of
方法定义输入,以制作不可修改的 LocalDateRange
列表。
List < LocalDateRange > dateRanges =
List.of(
LocalDateRange.of( LocalDate.of( 2019,1 ),LocalDate.of( 2019,1 ) ),LocalDateRange.of( LocalDate.of( 2019,// Not connected to the others.
LocalDateRange.of( LocalDate.of( 2018,31 ) ) // Earlier start,in previous year.
);
确定所涉及日期的总体范围,即第一个开始和最后一个结束。
请记住,我们正在处理一个日期范围列表 (LocalDateRange
),每个列表包含一对日期 (LocalDate
) 对象。比较器比较存储在每个 LocalDate
中的起始/结束 LocalDateRange
对象,以获得最小值或最大值。此处看到的 get
方法正在获取 LocalDateRange
,因此我们然后调用 getStart
/getEnd
来检索存储在其中的开始/结束 LocalDate
。
LocalDate start = dateRanges.stream().min( Comparator.comparing( localDateRange -> localDateRange.getStart() ) ).get().getStart();
LocalDate end = dateRanges.stream().max( Comparator.comparing( localDateRange -> localDateRange.getEnd() ) ).get().getEnd();
列出该时间间隔内的所有日期。 LocalDate#datesUntil
方法生成在一对开始日期和结束日期之间找到的 LocalDate
对象流。开始是包容的,而结束是独占的。
List < LocalDate > dates =
start
.datesUntil( end )
.collect( Collectors.toList() );
对于每个日期,获取包含该日期的日期范围列表。
Map < LocalDate,List < LocalDateRange > > mapDateToListOfDateRanges = new TreeMap <>();
for ( LocalDate date : dates )
{
List < LocalDateRange > hits = dateRanges.stream().filter( range -> range.contains( date ) ).collect( Collectors.toList() );
System.out.println( date + " ➡ " + hits ); // Visually interesting to see on the console.
mapDateToListOfDateRanges.put( date,hits );
}
对于每个日期,获取包含该日期的日期范围的计数。我们想要对我们放入上面地图的每个 List
进行计数。在我的问题 Report on a multimap by producing a new map of each key mapped to the count of elements in its collection value 中讨论了生成一个新地图,其值为原始地图中集合的计数,我从 Answer by Syco 中提取了代码。
Map < LocalDate,Integer > mapDateToCountOfDateRanges =
mapDateToListOfDateRanges
.entrySet()
.stream()
.collect(
Collectors.toMap(
( Map.Entry < LocalDate,List < LocalDateRange > > e ) -> { return e.getKey(); },( Map.Entry < LocalDate,List < LocalDateRange > > e ) -> { return e.getValue().size(); },( o1,o2 ) -> o1,TreeMap :: new
)
);
不幸的是,似乎没有办法让流通过最大值过滤映射中的多个条目。请参阅:Using Java8 Stream to find the highest values from map。
因此,首先我们要找到地图中一个或多个条目的值的最大值。
Integer max = mapDateToCountOfDateRanges.values().stream().max( Comparator.naturalOrder() ).get();
然后我们仅过滤具有该数字值的条目,将这些条目移动到新地图。
Map < LocalDate,Integer > mapDateToCountOfDateRangesFilteredByHighestCount =
mapDateToCountOfDateRanges
.entrySet()
.stream()
.filter( e -> e.getValue() == max )
.collect(
Collectors.toMap(
Map.Entry :: getKey,Map.Entry :: getValue,TreeMap :: new
)
);
转储到控制台。
System.out.println( "dateRanges = " + dateRanges );
System.out.println( "start/end = " + LocalDateRange.of( start,end ).toString() );
System.out.println( "mapDateToListOfDateRanges = " + mapDateToListOfDateRanges );
System.out.println( "mapDateToCountOfDateRanges = " + mapDateToCountOfDateRanges );
System.out.println( "mapDateToCountOfDateRangesFilteredByHighestCount = " + mapDateToCountOfDateRangesFilteredByHighestCount );
短期结果。
[警告:我没有手动验证这些结果。使用此代码风险自负,并自行验证。]
mapDateToCountOfDateRangesFilteredByHighestCount = {2019-03-01=3,2019-03-02=3,2019-03-03=3,2019-03-04=3,2019-03-05=3,-2019-03 06=3,2019-03-07=3,2019-03-08=3,2019-03-09=3,2019-03-10=3,2019-03-11=3,2019-03-12= 3,2019-03-13=3,2019-03-14=3,2019-03-15=3,2019-03-16=3,2019-03-17=3,2019-03-18=3,2019-03-19=3,2019-03-20=3,2019-03-21=3,2019-03-22=3,2019-03-23=3,2019-03-24=3,2019- 03-25=3,2019-03-26=3,2019-03-27=3,2019-03-28=3,2019-03-29=3,2019-03-30=3,2019-03- 31=3,2019-04-01=3,2019-04-02=3,2019-04-03=3,2019-04-04=3,2019-04-05=3,2019-04-06= 3,2019-04-07=3,2019-04-08=3,2019-04-09=3,2019-04-10=3,2019-04-11=3,2019-04-12=3,2019-04-13=3,2019-04-14=3,2019-04-15=3,2019-04-16=3,2019-04-17=3,2019-04-18=3,2019- 04-19=3,2019-04-20=3,2019-04-21=3,2019-04-22=3,2019-04-23=3,2019-04-24=3,2019-04- 25=3,2019-04-26=3,2019-04-27=3,2019-04-28=3,2019-04-29=3,2019-04-30=3}
完整代码
为了方便您复制粘贴,这里有一个完整的类来运行此示例代码。
package work.basil.example;
import org.threeten.extra.LocalDateRange;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
public class DateRanger
{
public static void main ( String[] args )
{
DateRanger app = new DateRanger();
app.demo();
}
private void demo ( )
{
// Input.
List < LocalDateRange > dateRanges =
List.of(
LocalDateRange.of( LocalDate.of( 2019,// Not connected to the others.
LocalDateRange.of( LocalDate.of( 2018,in previous year.
);
// Determine first start and last end.
LocalDate start = dateRanges.stream().min( Comparator.comparing( localDateRange -> localDateRange.getStart() ) ).get().getStart();
LocalDate end = dateRanges.stream().max( Comparator.comparing( localDateRange -> localDateRange.getEnd() ) ).get().getEnd();
List < LocalDate > dates =
start
.datesUntil( end )
.collect( Collectors.toList() );
// For each date,get a list of the date-dateRanges containing that date.
Map < LocalDate,List < LocalDateRange > > mapDateToListOfDateRanges = new TreeMap <>();
for ( LocalDate date : dates )
{
List < LocalDateRange > hits = dateRanges.stream().filter( range -> range.contains( date ) ).collect( Collectors.toList() );
System.out.println( date + " ➡ " + hits ); // Visually interesting to see on the console.
mapDateToListOfDateRanges.put( date,hits );
}
// For each of those dates,get a count of date-ranges containing that date.
Map < LocalDate,Integer > mapDateToCountOfDateRanges =
mapDateToListOfDateRanges
.entrySet()
.stream()
.collect(
Collectors.toMap(
( Map.Entry < LocalDate,TreeMap :: new
)
);
// Unfortunately,there seems to be no way to get a stream to filter more than one entry in a map by maximum value.
// So first we find the maximum number in a value for one or more entries of our map.
Integer max = mapDateToCountOfDateRanges.values().stream().max( Comparator.naturalOrder() ).get();
// Then we filter for only entries with a value of that number,moving those entries to a new map.
Map < LocalDate,Integer > mapDateToCountOfDateRangesFilteredByHighestCount =
mapDateToCountOfDateRanges
.entrySet()
.stream()
.filter( e -> e.getValue() == max )
.collect(
Collectors.toMap(
Map.Entry :: getKey,TreeMap :: new
)
);
System.out.println( "dateRanges = " + dateRanges );
System.out.println( "start/end = " + LocalDateRange.of( start,end ).toString() );
System.out.println( "mapDateToListOfDateRanges = " + mapDateToListOfDateRanges );
System.out.println( "mapDateToCountOfDateRanges = " + mapDateToCountOfDateRanges );
System.out.println( "mapDateToCountOfDateRangesFilteredByHighestCount = " + mapDateToCountOfDateRangesFilteredByHighestCount );
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。