如何从给定日期集中找到每个月的最小日期

5

我需要从给定的日期数组中提取每个月最小的日期。

例如- 日期格式为dd/MM/yyyy 以下是样本输入

04/11/2019,  11/11/2019, 18/11/2019, 25/11/2019, 02/12/2019, 09/12/2019, 
06/01/2020, 03/02/2020, 10/02/2020, 17/02/2020, 24/02/2020, 02/03/2020, 
09/03/2020, 16/03/2020, 23/03/2020, 30/03/2020, 06/04/2020, 13/04/2020, 
20/04/2020, 27/04/2020

我需要从每个月中获取最小日期:

输出结果应如下 -

04/11/2019, 02/12/2019, 06/01/2020, 03/02/2020, 02/03/2020, 06/04/2020

有人可以帮忙吗?


1
你尝试过什么了吗? - MC Emperor
我无法构建相同的逻辑。找不到确切的解决方案。 - DevSay
1
不要一开始就将日期放入字符串列表中,而是应该将它们放入LocalDate的列表中。 - Ole V.V.
5个回答

5
如果您正在使用Java-8,您可以使用以下内容:
List<LocalDate> collect = Stream.of(strings)
        .map(s -> LocalDate.parse(s, format)) // convert your strings to LocalDate
        .collect(Collectors.groupingBy(YearMonth::from)) // group by year and month
        .values().stream()
        .map(a -> a.stream().sorted().limit(1).findFirst().get())
        .sorted()
        .collect(Collectors.toList()); // collect the results

输出

[2019-11-04, 2019-12-02, 2020-01-06, 2020-02-03, 2020-03-02, 2020-04-06]

Ideone演示


@Volokh 我的回答并不是为了解析字符串的方法,在演示中我使用了一个技巧来获取日期,OP应该有一个字符串列表。 - Youcef LAIDANI
1
是的,你说得对。我不知道为什么,但我认为问题是要找到每个月最新的日期,而不是最早的。抱歉,我的错误。 - ruslanys
@YCF_L 现在你的代码运行正常,我移除了我的 DV :) 那么“OP”是什么意思? - ruslanys
2
@ruslanys OP 意思是原帖发布者,在这种情况下是提出问题的人 :) - Youcef LAIDANI
是的 @YCF_L,我在我的端上尝试了一下。它会将输入字符串列表原样返回。 - DevSay
显示剩余9条评论

3
public List<LocalDate> filterDates(List<LocalDate> dates) {
    return dates.stream()
            .collect(Collectors.groupingBy(YearMonth::from))
            .values()
            .stream()
            .map(Collections::min) // .map(Collections::max)
            .collect(Collectors.toList());
}

你可以使用以下方法代替.map(Collections::min):

.map(list -> {
    list.sort(Comparator.naturalOrder());
    return list.get(0);
})

或者:

.map(list -> list.stream().max(Comparator.naturalOrder()))

1
这个解决方案对我很有用。只需要更改 "return list.get(list.size()-1)",就可以从列表中获取每个月份的最后一个日期,从而解决了我的第二个问题。非常感谢您的帮助。 - DevSay
1
请注意,list.sort(...)可能会根据groupingBy返回的列表类型抛出异常。使用list.stream()。sorted()...可能更安全且更具未来性。 - assylias
2
你不需要对整个内部列表进行排序来查找最小值(或最大值)。例如,可以使用.map(Collections::min) - Ole V.V.
3
我会选择 list.stream().max(Comparator.naturalOrder()),而非之前提到的方法。 - ruslanys
@ruslanys 是否可以使用HashMap<String,LocalDate>来代替List<LocalDate>执行相同的操作? - DevSay
@Tinks_say 确定 - ruslanys

3

已经有许多好的答案了。除此之外,我想再补充以下内容:

  1. 不要将字符串放在原始列表中。放置 LocalDate 对象。如果您获得字符串输入,请在添加到列表之前解析它。
  2. 您不需要对内部列表进行排序以获取其最小值(或最大值)。

在 Java 10 或更高版本中:

    List<LocalDate> input = List.of(LocalDate.of(2019, 11, 4), 
            LocalDate.of(2019, 11, 11), LocalDate.of(2019, 11, 18),
            LocalDate.of(2019, 11, 25), LocalDate.of(2019, 12, 2),
            LocalDate.of(2019, 12, 9), LocalDate.of(2020, 1, 6),
            LocalDate.of(2020, 2, 3), LocalDate.of(2020, 2, 10),
            LocalDate.of(2020, 2, 17), LocalDate.of(2020, 2, 24),
            LocalDate.of(2020, 3, 2), LocalDate.of(2020, 3, 9),
            LocalDate.of(2020, 3, 16), LocalDate.of(2020, 3, 23),
            LocalDate.of(2020, 3, 30), LocalDate.of(2020, 4, 6),
            LocalDate.of(2020, 4, 13), LocalDate.of(2020, 4, 20),
            LocalDate.of(2020, 4, 27));

    List<LocalDate> minDatePerMonth = input.stream()
            .collect(Collectors.groupingBy(YearMonth::from, Collectors.minBy(Comparator.naturalOrder())))
            .values()
            .stream()
            .map(Optional::orElseThrow)
            .sorted()
            .collect(Collectors.toList());

我正在使用重载版本的Collectors.groupingBy,它接受一个“下游收集器”。groupingBy产生一个映射(map),但它的值不是列表,而是由下游收集器产生的。在这种情况下,我们使用Collectors.minBy,它会产生一个包含Optional<LocalDate>的结果。由于groupingBy不会创建没有至少一个日期的映射条目,所以我们知道Optional不能为空。

当然,我们希望为用户提供格式良好的输出。我们可以通过以下方式实现:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");
    String output = minDatePerMonth.stream()
            .map(formatter::format)
            .collect(Collectors.joining(", "));
    System.out.println(output);

2019年4月11日,2019年2月12日,2020年6月1日,2020年3月2日,2020年2月3日,2020年4月6日

在 Java 9 中

Java 10 中引入了无参的 Optional.orElseThrow。在 Java 8 和 9 中,您可以使用例如:

            .map(old -> old.orElseThrow(() -> new IllegalStateException("This can’t happen")))

在Java 8中,List.of还未被引入。但是您已经获得了列表,因此我会考虑如何在问题之外进行初始化。如果需要,在Java 8中初始化列表的具体方法请读者自行寻找。

1
嗨Ole,你的回答很好,我认为你不需要使用map(Optional::orElseThrow),因为分组应该总是会返回至少一个值。 - Youcef LAIDANI
1
我需要一种方法来解开Optional,@YCF_L。我更喜欢Java 10中的orElseThrow而不是get。它们的作用相同。你是对的,异常永远不会被抛出。 - Ole V.V.
为什么选择Java 10?这段代码的哪一部分不受Java 8或9支持? - Basil Bourque
@BasilBourque 我相信Java 10引入了无参的orElseThrow。在Java 8或9中,可以使用get或带有1个参数的lambda表达式。List.of来自Java 9,但在Java 8中,您肯定可以找到一种初始化列表的方法。 - Ole V.V.
@BasilBourque 我已经编辑并且添加了必要的更改来支持Java 8或9。非常感谢您的启发性问题。 - Ole V.V.

1

以下是您需要执行的步骤:

  1. 将您的字符串解析为LocalDate
  2. 构建一个映射,其中您的键将是一个月份,而您的值将是一个排序列表。
    1. 遍历所有列表,并从每个列表中取出第一个值。

这就是您的算法。我会留下实现给您。


1

//localDates是LocalDate的列表。您可以进行转换。

localDates.stream().forEach(localDate -> {
            Integer month = localDate.getMonthValue();
            if (localDateMap.containsKey(month)){
                if(localDate.isBefore(localDateMap.get(month))){
                    localDateMap.put(month,localDate);
                }
            }else{
                localDateMap.put(month,localDate);
            }
        });

然后从地图中获取所有的值。

由于您只使用了 getMonthValue(),这将把2019年3月的日期与2020年3月的日期分组,并仅保留其中最早的日期,即2019年的日期(在此示例中)。 - Ole V.V.
作为一个细节,不要使用流。只需使用 localDates.forEach 等。 - Ole V.V.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接