【问题标题】:Sorting collection date field between two given dates在两个给定日期之间对收集日期字段进行排序
【发布时间】:2021-10-12 10:30:53
【问题描述】:

我想根据两天之间的日期字段对列表进行排序,比如从现在到接下来的 3 天。排序后的列表应按升序排列从现在起的天数和接下来的 3 天,之后按升序排列所有以前和未来的日期。

例如:

List<LocalDate> list = Arrays.asList(
            LocalDate.of(2021, 8, 1),
            LocalDate.of(2021, 8, 2),
            LocalDate.of(2021, 8, 3),
            LocalDate.of(2021, 8, 8),
            LocalDate.of(2021, 8, 9),
            LocalDate.of(2021, 8, 10),
            LocalDate.of(2021, 8, 11),
            LocalDate.of(2021, 8, 12),
            LocalDate.of(2021, 8, 13),
            LocalDate.of(2021, 8, 14),
            LocalDate.of(2021, 8, 15),
            null);`   

假设今天是 2021 年 8 月 8 日 输出将是:

null(null 日期始终位于顶部)
2021 年 8 月 8 日
2021 年 8 月 9 日
2021 年 8 月 10 日(接下来 3 天到此处按升序排列)
2021 年 8 月 8 日(从这里开始,所有过去和未来的日期都按升序排列)
2021 年 8 月 1 日
2021 年 8 月 2 日
2021 年 8 月 3 日
2021 年 8 月 11 日
2021 年 8 月 12 日
2021 年 8 月 13 日
2021 年 8 月 14 日
2021 年 8 月 15 日

Comparator<LocalDate> comp = (o1, o2) -> {
    if (o1.isBefore(now) && o2.isBefore(now) || o1.isAfter(now.plusDays(3)) && o2.isAfter(now.plusDays(3))) {
        return o1.compareTo(o2);
    }
    return o2.compareTo(o1);
};

【问题讨论】:

  • 示例输出是否有意重复日期8-Aug-2021,在示例输入中仅出现一次?

标签: java sorting collections java-8 localdate


【解决方案1】:

由于您说要排序,并且您的示例代码是 Comparator 实现,我将假设示例输出中日期 8-Aug-2021 的重复是无意的。 真正的排序当然只会重新排序输入对象,而不是复制它们或引入新的对象。

您正在寻找的只是一个三键排序。第一个键将null 与非null 进行比较。第二个键是基于日期是否在所选窗口中——那些排在前面的那些不是。第三个是日期的自然顺序。

主要的问题是,为了确保在整个排序过程中一致地计算第一个键,您必须只计算一次当前日期,在 Comparator 被实例化时。这将是Comparator 的一个属性,因为现有实例不会自动调整到系统时钟滚动到新日期。

例如:

final LocalDate today = LocalDate.now();

Comparator<LocalDate> comp = (o1, o2) -> {

    if (o1 == null) {
        return (o2 == null) ? 0 : -1;
    } else if (o2 == null) {
        // At this point, o1 is known to be non-null
        return 1;
    } else {
        // Note: plusDays(2) because that's an _inclusive_ end date for the 3-day
        // range
        boolean outsideWindow1 = o1.isBefore(today) || o1.isAfter(today.plusDays(2));
        boolean outsideWindow2 = o2.isBefore(today) || o2.isAfter(today.plusDays(2));

        if (outsideWindow1 != outsideWindow2) {
            // One date is inside the window and the other outside. then
            // The one inside is sorted ahead of the other
            return outsideWindow1 ? 1 : -1;
        } else {
            // Either both dates are in the 3-day window or both are outside it.
            // Sort by their natural order
            return o1.compareTo(o2);
        }
    }
};

【讨论】:

    【解决方案2】:

    Comparator.nullsFirst()、.comparing() 和 .thenComparing()

    我喜欢 John Bollinger 使用高级比较器的想法。这是我尽可能优雅地表达它的尝试。

        LocalDate today = LocalDate.now(ZoneId.systemDefault());
        LocalDate inThreeDays = today.plusDays(3);
        Function<LocalDate, Boolean> notWithinNext3Days = ld -> ld.isBefore(today) || ld.isAfter(inThreeDays);
        Comparator<LocalDate> myComparator = Comparator.nullsFirst(
                Comparator.comparing(notWithinNext3Days)
                        .thenComparing(LocalDate::compareTo));
    

    如果代码在午夜运行,为了获得一致的结果,我们只需要读取一次时钟。我正在利用 Boolean 对象的自然排序在 true 之前是 false 的事实。让我们用你的列表试试吧。

        list.sort(myComparator);
        list.forEach(System.out::println);
    

    在 8 月 9 日运行,输出为:

    null
    2021-08-09
    2021-08-10
    2021-08-11
    2021-08-12
    2021-08-01
    2021-08-02
    2021-08-03
    2021-08-08
    2021-08-13
    2021-08-14
    2021-08-15
    

    如您所见,总共 4 天在 null 和其余时间(8 月 9 日到 12 日)之间排序。我不知道现在和接下来的 3 天是否总共意味着 4 天的窗口。我相信任何使用我的答案的人都会根据需要进行调整。

    【讨论】:

    • 我喜欢这个(+1):它比我的答案中更手动的比较器实现更干净、更清晰。但是,它似乎与请求有一点不同:它将一个 天的日期窗口拉到结果的前面,而不是一个三天的窗口。
    【解决方案3】:

    你可以使用Stream#concat,如下图:

    List<LocalDate> result = 
        Stream.concat(
            list.stream()
                .filter(Objects::nonNull)
                .filter(date -> !date.isBefore(today) && !date.isAfter(thirdDay)),
            Stream.concat(
                Stream.of(today),
                list.stream()
                    .filter(Objects::nonNull)
                    .filter(date -> 
                        date.isBefore(today) || date.isAfter(thirdDay)
                    ).sorted()
            )
        ).collect(Collectors.toList());
    

    ONLINE DEMO

    如果你不想重复今天的日期,你可以这样做:

    List<LocalDate> result = 
        Stream.concat(
            list.stream()
                .filter(Objects::nonNull)
                .filter(date -> !date.isBefore(today) && !date.isAfter(thirdDay)),
            list.stream()
                .filter(Objects::nonNull)
                .filter(date -> date.isBefore(today) || date.isAfter(thirdDay))
                .sorted()
        ).collect(Collectors.toList());
    

    ONLINE DEMO

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-07-01
      • 2017-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多