将 LocalDates 地图的内容分组为日期范围地图
Group the contents of a Map of LocalDates into a Map of Ranges of dates
我有一张按天索引的工作班次图 Map<LocalDate, Collection<Shift>> shiftsAllDays
如:
"2020-03-26": [
{
"id": 4,
"startTime": "21:00:00"
},
{
"id": 5,
"startTime": "09:00:00",
}
],
"2020-03-27": [
{
"id": 4,
"startTime": "22:00:00"
},
{
"id": 5,
"startTime": "10:00:00",
}
],
...
]
可以看出,班次有一个 ID 和一个开始时间。
但是按天为地图编制索引是一种浪费:除了一件事之外,班次恰好始终相同。开始时间是 LocalTime
,因此班次的唯一区别是在应用时区后,产生的班次仅在发生 DST 翻转时在小时上有所不同。
我想在收集器中做一些 groupBy
类似的事情:
Range.of(2020-01-01 ... 2020-03-26) [
{
"id": 4,
"startTime": "21:00:00"
},
{
"id": 5,
"startTime": "09:00:00",
}
],
Range.of(2020-03-27 ... 2020-10-30) [
{
"id": 4,
"startTime": "22:00:00"
},
{
"id": 5,
"startTime": "10:00:00",
}
],
Range.of(2020-10-31 ... 2021-01-01) [
{
"id": 4,
"startTime": "21:00:00"
},
{
"id": 5,
"startTime": "09:00:00",
}
]
...从 DST 翻转到 DST 翻转分组在 Ranges
中。我很难编写 Collectors.groupBy(...)
并将密钥从 LocalDate
天转换为 Range<LocalDate>
具有自定义范围的解决方案 class
首先,您需要定义一个 class Range
,其中包含两个字段(开始日期 和 结束日期),并创建一个范围列表。由于只需要三个实例,因此将此列表声明为实用程序 class.
中的 public static final
字段是有意义的
下面是Range
的例子,为了简洁起见,我把它实现为Java16条记录。给出了一种实用方法,用于检查给定日期是否在 range:
的此实例内
public record Range(LocalDate start, LocalDate end) {
public boolean isWithinRange(LocalDate date) { // start inclusive, end exclusive
return date.isBefore(start) || date.isBefore(end) && date.isAfter(start);
}
}
范围列表:
public static final List<Range> RANGES =
List.of(new Range(LocalDate.of(2020,1, 1), LocalDate.of(2020,3, 27)),
new Range(LocalDate.of(2020,3, 27), LocalDate.of(2020,10, 31)),
new Range(LocalDate.of(2020,10, 31), LocalDate.of(2021,1, 1)));
实用程序方法 getRange()
负责根据给定日期从范围列表中检索 Range
的实例:
public static Range getRange(LocalDate data) {
return RANGES.stream()
.filter(range -> range.isWithinRange(data))
.findFirst()
.orElseThrow();
}
虚拟记录Shift
(用于测试目的):
public record Shift(int id, LocalTime startTime) {}
为了使用流将 Map<LocalDate, Collection<Shift>>
转换为 Map<Range, List<Shift>>
,我们需要创建映射条目流。比起使用收集器 groupingBy()
来按 范围 对数据进行分组。作为 groupingBy()
的下游收集器,我们必须提供 flatMapping()
以便展平与特定数据关联的数据(以便所有 Shift
对象将映射到相同的 range 将被放置在同一个集合中)。在 flatMapping()
的下游,我们需要提供一个收集器,它定义如何存储( 或如何执行缩减 )扁平元素。数据中collector toList()
用于将映射到同一个key的数据存储在一个列表中
main()
- 演示
public static void main(String[] args) {
Map<LocalDate, Collection<Shift>> shiftsByDate =
Map.of(LocalDate.of(2020,3, 26),
List.of(new Shift(4, LocalTime.of(21, 0, 0)), new Shift(5, LocalTime.of(9, 0, 0))),
LocalDate.of(2020,3, 27),
List.of(new Shift(4, LocalTime.of(22, 0, 0)), new Shift(5, LocalTime.of(10, 0, 0)))
);
Map<Range, List<Shift>> shiftsByRange = shiftsByDate.entrySet().stream()
.collect(Collectors.groupingBy(entry -> getRange(entry.getKey()),
Collectors.flatMapping(entry -> entry.getValue().stream(),
Collectors.toList())));
shiftsByRange.forEach((k, v) -> System.out.println(k + " : " + v));
}
输出
Range[start=2020-10-31, end=2021-01-01] : [Shift[id=4, startTime=22:00], Shift[id=5, startTime=10:00]]
Range[start=2020-01-01, end=2020-03-27] : [Shift[id=4, startTime=21:00], Shift[id=5, startTime=09:00]]
范围来自 Spring 数据的解决方案
以前的版本只需要适度的更改即可使用 Spring 数据项目中的 Range<T>
。我们需要修复实例化这些对象的列表 RANGES
,并将类型 Range
替换为 Range<T>
.
注意事项: 如果您希望泛型类型参数 <T>
为 LocalDate
- 这是不正确的,也不会起作用。
Range<T>
class 期望其泛型类型是可比较的,即 T extends Comparable<T>
和 LocalDate
不直接扩展可比性,它实现了 ChronoLocalDate
接口它又实现了 Comparable
接口,而 LocalDate
继承了 compareTo()
.
的默认实现
换句话说:
- 不正确
LocalDate
扩展 Comparable<LocalDate>
- 因为实际上
LocalDate
扩展 Comparable<ChronoLocalDate>
.
因此,我们不得不使用ChronoLocalDate
作为泛型。
RANGES
列表如下所示:
public static final List<Range<ChronoLocalDate>> RANGES =
List.of(Range.rightOpen(LocalDate.of(2020,1, 1), LocalDate.of(2020,3, 27)),
Range.rightOpen(LocalDate.of(2020,3, 27), LocalDate.of(2020,10, 31)),
Range.rightOpen(LocalDate.of(2020,10, 31), LocalDate.of(2021,1, 1)));
方法 getRange()
负责根据给定日期从范围列表中检索 Range
的实例:
public static Range<ChronoLocalDate> getRange(LocalDate data) {
return RANGES.stream()
.filter(range -> range.contains(data))
.findFirst()
.orElseThrow();
}
main()
- 演示
public static void main(String[] args) {
Map<LocalDate, Collection<Shift>> shiftsByDate =
Map.of(LocalDate.of(2020,3, 26),
List.of(new Shift(4, LocalTime.of(21, 0, 0)), new Shift(5, LocalTime.of(9, 0, 0))),
LocalDate.of(2020,3, 27),
List.of(new Shift(4, LocalTime.of(22, 0, 0)), new Shift(5, LocalTime.of(10, 0, 0)))
);
Map<Range<ChronoLocalDate>, List<Shift>> shiftsByRange = shiftsByDate.entrySet().stream()
.collect(Collectors.groupingBy(entry -> getRange(entry.getKey()),
Collectors.flatMapping(entry -> entry.getValue().stream(),
Collectors.toList())));
shiftsByRange.forEach((k, v) -> System.out.println(k + " : " + v));
}
输出:
[2020-03-27-2020-10-31) : [Shift[id=4, startTime=22:00], Shift[id=5, startTime=10:00]]
[2020-01-01-2020-03-27) : [Shift[id=4, startTime=21:00], Shift[id=5, startTime=09:00]]
我有一张按天索引的工作班次图 Map<LocalDate, Collection<Shift>> shiftsAllDays
如:
"2020-03-26": [
{
"id": 4,
"startTime": "21:00:00"
},
{
"id": 5,
"startTime": "09:00:00",
}
],
"2020-03-27": [
{
"id": 4,
"startTime": "22:00:00"
},
{
"id": 5,
"startTime": "10:00:00",
}
],
...
]
可以看出,班次有一个 ID 和一个开始时间。
但是按天为地图编制索引是一种浪费:除了一件事之外,班次恰好始终相同。开始时间是 LocalTime
,因此班次的唯一区别是在应用时区后,产生的班次仅在发生 DST 翻转时在小时上有所不同。
我想在收集器中做一些 groupBy
类似的事情:
Range.of(2020-01-01 ... 2020-03-26) [
{
"id": 4,
"startTime": "21:00:00"
},
{
"id": 5,
"startTime": "09:00:00",
}
],
Range.of(2020-03-27 ... 2020-10-30) [
{
"id": 4,
"startTime": "22:00:00"
},
{
"id": 5,
"startTime": "10:00:00",
}
],
Range.of(2020-10-31 ... 2021-01-01) [
{
"id": 4,
"startTime": "21:00:00"
},
{
"id": 5,
"startTime": "09:00:00",
}
]
...从 DST 翻转到 DST 翻转分组在 Ranges
中。我很难编写 Collectors.groupBy(...)
并将密钥从 LocalDate
天转换为 Range<LocalDate>
具有自定义范围的解决方案 class
首先,您需要定义一个 class Range
,其中包含两个字段(开始日期 和 结束日期),并创建一个范围列表。由于只需要三个实例,因此将此列表声明为实用程序 class.
public static final
字段是有意义的
下面是Range
的例子,为了简洁起见,我把它实现为Java16条记录。给出了一种实用方法,用于检查给定日期是否在 range:
public record Range(LocalDate start, LocalDate end) {
public boolean isWithinRange(LocalDate date) { // start inclusive, end exclusive
return date.isBefore(start) || date.isBefore(end) && date.isAfter(start);
}
}
范围列表:
public static final List<Range> RANGES =
List.of(new Range(LocalDate.of(2020,1, 1), LocalDate.of(2020,3, 27)),
new Range(LocalDate.of(2020,3, 27), LocalDate.of(2020,10, 31)),
new Range(LocalDate.of(2020,10, 31), LocalDate.of(2021,1, 1)));
实用程序方法 getRange()
负责根据给定日期从范围列表中检索 Range
的实例:
public static Range getRange(LocalDate data) {
return RANGES.stream()
.filter(range -> range.isWithinRange(data))
.findFirst()
.orElseThrow();
}
虚拟记录Shift
(用于测试目的):
public record Shift(int id, LocalTime startTime) {}
为了使用流将 Map<LocalDate, Collection<Shift>>
转换为 Map<Range, List<Shift>>
,我们需要创建映射条目流。比起使用收集器 groupingBy()
来按 范围 对数据进行分组。作为 groupingBy()
的下游收集器,我们必须提供 flatMapping()
以便展平与特定数据关联的数据(以便所有 Shift
对象将映射到相同的 range 将被放置在同一个集合中)。在 flatMapping()
的下游,我们需要提供一个收集器,它定义如何存储( 或如何执行缩减 )扁平元素。数据中collector toList()
用于将映射到同一个key的数据存储在一个列表中
main()
- 演示
public static void main(String[] args) {
Map<LocalDate, Collection<Shift>> shiftsByDate =
Map.of(LocalDate.of(2020,3, 26),
List.of(new Shift(4, LocalTime.of(21, 0, 0)), new Shift(5, LocalTime.of(9, 0, 0))),
LocalDate.of(2020,3, 27),
List.of(new Shift(4, LocalTime.of(22, 0, 0)), new Shift(5, LocalTime.of(10, 0, 0)))
);
Map<Range, List<Shift>> shiftsByRange = shiftsByDate.entrySet().stream()
.collect(Collectors.groupingBy(entry -> getRange(entry.getKey()),
Collectors.flatMapping(entry -> entry.getValue().stream(),
Collectors.toList())));
shiftsByRange.forEach((k, v) -> System.out.println(k + " : " + v));
}
输出
Range[start=2020-10-31, end=2021-01-01] : [Shift[id=4, startTime=22:00], Shift[id=5, startTime=10:00]]
Range[start=2020-01-01, end=2020-03-27] : [Shift[id=4, startTime=21:00], Shift[id=5, startTime=09:00]]
范围来自 Spring 数据的解决方案
以前的版本只需要适度的更改即可使用 Spring 数据项目中的 Range<T>
。我们需要修复实例化这些对象的列表 RANGES
,并将类型 Range
替换为 Range<T>
.
注意事项: 如果您希望泛型类型参数 <T>
为 LocalDate
- 这是不正确的,也不会起作用。
Range<T>
class 期望其泛型类型是可比较的,即 T extends Comparable<T>
和 LocalDate
不直接扩展可比性,它实现了 ChronoLocalDate
接口它又实现了 Comparable
接口,而 LocalDate
继承了 compareTo()
.
换句话说:
- 不正确
LocalDate
扩展Comparable<LocalDate>
- 因为实际上
LocalDate
扩展Comparable<ChronoLocalDate>
.
因此,我们不得不使用ChronoLocalDate
作为泛型。
RANGES
列表如下所示:
public static final List<Range<ChronoLocalDate>> RANGES =
List.of(Range.rightOpen(LocalDate.of(2020,1, 1), LocalDate.of(2020,3, 27)),
Range.rightOpen(LocalDate.of(2020,3, 27), LocalDate.of(2020,10, 31)),
Range.rightOpen(LocalDate.of(2020,10, 31), LocalDate.of(2021,1, 1)));
方法 getRange()
负责根据给定日期从范围列表中检索 Range
的实例:
public static Range<ChronoLocalDate> getRange(LocalDate data) {
return RANGES.stream()
.filter(range -> range.contains(data))
.findFirst()
.orElseThrow();
}
main()
- 演示
public static void main(String[] args) {
Map<LocalDate, Collection<Shift>> shiftsByDate =
Map.of(LocalDate.of(2020,3, 26),
List.of(new Shift(4, LocalTime.of(21, 0, 0)), new Shift(5, LocalTime.of(9, 0, 0))),
LocalDate.of(2020,3, 27),
List.of(new Shift(4, LocalTime.of(22, 0, 0)), new Shift(5, LocalTime.of(10, 0, 0)))
);
Map<Range<ChronoLocalDate>, List<Shift>> shiftsByRange = shiftsByDate.entrySet().stream()
.collect(Collectors.groupingBy(entry -> getRange(entry.getKey()),
Collectors.flatMapping(entry -> entry.getValue().stream(),
Collectors.toList())));
shiftsByRange.forEach((k, v) -> System.out.println(k + " : " + v));
}
输出:
[2020-03-27-2020-10-31) : [Shift[id=4, startTime=22:00], Shift[id=5, startTime=10:00]]
[2020-01-01-2020-03-27) : [Shift[id=4, startTime=21:00], Shift[id=5, startTime=09:00]]