Java - 流过滤器以获取日期列表中几个月的最后一天(不是日历最后一天)

Java - Stream Filter to get Last Day of several months in a list of dates (not calendar last day)

查询: 筛选下面的数据以在列表中查找每个月的最后日期。请注意,在这种情况下, 数据中月份的最后日期可能或 可能与日历月的最后日期不匹配。 预期输出显示在第二个列表中。

研究:

我希望这个问题很清楚,并为我指明了如何使用流来完成的方向, 因为我不想使用 for 循环。

示例数据:

Date Model Start End
27-11-1995 ABC 241 621
27-11-1995 XYZ 3456 7878
28-11-1995 ABC 242 624
28-11-1995 XYZ 3457 7879
29-11-1995 ABC 243 627
29-11-1995 XYZ 3458 7880
30-11-1995 ABC 244 630
30-11-1995 XYZ 3459 7881
01-12-1995 ABC 245 633
01-12-1995 XYZ 3460 7882
04-12-1995 ABC 246 636
04-12-1995 XYZ 3461 7883
27-12-1995 ABC 247 639
27-12-1995 XYZ 3462 7884
28-12-1995 ABC 248 642
28-12-1995 XYZ 3463 7885
29-12-1995 ABC 249 645
29-12-1995 XYZ 3464 7886
01-01-1996 ABC 250 648
01-01-1996 XYZ 3465 7887
02-01-1996 ABC 251 651
02-01-1996 XYZ 3466 7888
29-01-1996 ABC 252 654
29-01-1996 XYZ 3467 7889
30-01-1996 ABC 253 657
30-01-1996 XYZ 3468 7890
31-01-1996 ABC 254 660
31-01-1996 XYZ 3469 7891

Screenshot

输出要求:

Date Model Start End
30-11-1995 ABC 244 630
30-11-1995 XYZ 3459 7881
29-12-1995 ABC 249 645
29-12-1995 XYZ 3464 7886
31-01-1996 ABC 254 660
31-01-1996 XYZ 3469 7891

Screenshot

我建议创建一个 Map<YearMonth, List<LocalDate> 并解析所有日期并填充地图。之后,您对所有列表进行排序,每个列表中的最后一个(或第一个取决于排序顺序)值将是您想要的值

嗯,groupingBymaxBy 的组合可能就可以了。

我假设 table 的每条记录都是 Event:

类型
record Event(LocalDate date, String model, int start, int end) { }

要获取 table 内的月份的最后几天,我们可以利用 groupingBy。为了对其进行分组,我们可以首先创建一个分组类型。下面,我创建了一个 EventGrouping 记录 1,使用 static 方法将 Event 转换为 EventGrouping。您想要的输出表明您希望按年-月-模型组合进行分组,因此我们只选择了这两个属性:

public record EventGrouping(YearMonth yearMonth, String model) {
        
    public static EventGrouping fromEvent(Event event) {
        return new EventGrouping(YearMonth.from(event.date()), event.model());
    }
}

然后,我们可以得到我们想要的结果:

events.stream()
    .collect(Collectors.groupingBy(
        EventGrouping::fromEvent,
        Collectors.maxBy(Comparator.comparing(Event::date))
    ));

这里发生的是所有流元素都由我们的 EventGrouping 分组,然后选择每个事件组的“最大值”。最大值当然是那个月的最近日期。

注意 maxBy returns 和 Optional,用于组为空的情况。另请注意,结果 Map 是无序的。

我们可以分别使用 collectingAndThenmap factory 来解决这两个问题:

Map<EventGrouping, Event> map = events.stream()
    .collect(groupingBy(
        EventGrouping::fromEvent,
        () -> new TreeMap<>(Comparator.comparing(EventGrouping::yearMonth)
            .thenComparing(EventGrouping::model)),
        collectingAndThen(maxBy(Comparator.comparing(Event::date)), Optional::get)
    ));

注意:groupingBycollectingAndThenmaxBy 都是从 java.util.stream.Collectors 静态导入的。

  • 我们添加了一个 SupplierTreeMapTreeMap 是一个 Map 实现,具有给定比较器的 predictable 顺序。这允许我们迭代按年-月-模型排序的结果条目。
  • collectingAndThen 允许我们将函数应用于给定 Collector 的结果。如前所述,maxBy returns 和 Optional,因为如果源流中没有元素,则 maxBy 不适用。然而,在我们的例子中,这永远不会发生。所以我们可以安全地将 Optional 映射到它包含的值。

1 除了编写自定义类型,您还可以使用现有的 class 来保存两个任意值,例如 Map.EntryPair 甚至 List<Object>.