【问题标题】:GroupingBy a range dates按范围日期分组
【发布时间】:2021-11-16 15:14:25
【问题描述】:

我有这样的情况:

List<ObjectA> collapsed = list.stream().collect(Collectors.collectingAndThen(
        Collectors.groupingBy(
                ObjectA::getDate,
                Collectors.maxBy(Comparator
                        .comparing(ObjectA::getPriority1)
                        .thenComparing(ObjectA::getPriority2)
                        .thenComparing(ObjectA::getDate)
                        .thenComparing(ObjectA::getId))),
        map -> map.values().stream()
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toList())));

它在相同的日期完美运行。 我只需要为某个日期窗口分组项目:

Integer daysWindow = 90;

所以我希望在这个窗口中的日期发生一切。 这怎么可能?

谢谢大家!

【问题讨论】:

  • 按年份 (*4+) 分组,然后按年份 / 90 或(月数 - 1)/ 3 分组。

标签: java java-stream grouping


【解决方案1】:

作为另一种方法,如果您要查找的是日期范围到相应值的映射,则可以通过创建范围数据类型,然后在该类型上生成组来给它更多的工作。这是一个例子:

class DateRange {
    private final LocalDate from;
    private final int length;

    public DateRange(LocalDate from, int length) {
        super();
        this.from = from;
        this.length = length;
    }

    //getters

    @Override
    public int hashCode() {
        return this.from.hashCode(); //TODO
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof DateRange))
            return false;
        
        DateRange r = (DateRange) obj;

        return this.length == r.length && this.from.equals(r.from);
    }

    @Override
    public String toString() {
        return "(" + from + " to " + from.plusDays(length) + ")";
    }
}

然后,您的流可以创建一个映射,就像您当前拥有的那样,但在确定每个元素所属的范围之后,按日期范围分组。

int daysWindow = 60;
var start = LocalDate.of(2021, 1, 1);

//example date list
List<LocalDate> dates = List.of(start.plusDays(20), 
        start.plusDays(30), start.plusDays(40), start.plusDays(50),
        start.plusDays(60), start.plusDays(70), start.plusDays(80));

var ranges = dates.stream()
        .collect(Collectors.groupingBy(d -> {
            var diff = ChronoUnit.DAYS.between(start, d);
            return new DateRange(start.plusDays(diff - diff % daysWindow), daysWindow);
        }));

当然,你会使用你的当前收集器,我只是想展示对groupingBy 的调用,你仍然可以使用下游收集器调用它。

您需要确定的一件事是start 的值。我假设这可以设置为集合中的最小日期,尽管它也可以是用户定义的。


以上代码的输出如下:

{(2021-03-02 to 2021-05-01)=[2021-03-02, 2021-03-12, 2021-03-22], 
 (2021-01-01 to 2021-03-02)=[2021-01-21, 2021-01-31, 2021-02-10, 2021-02-20]}

当使用不同的窗口进行测试时,int daysWindow = 90;:

{(2021-01-01 to 2021-04-01)=[2021-01-21, 2021-01-31, 2021-02-10, 
                             2021-02-20, 2021-03-02, 2021-03-12, 2021-03-22]}

【讨论】:

    【解决方案2】:

    您可以在开头应用过滤器,如下所示,保持其余内容相同。

    LocalDate today = LocalDate.now();
    
    list.stream().filter(obj -> !obj.getDate().isAfter(today.plusDays(90)))
        .collect(Collectors.collectingAndThen(....
    

    注意:我假设 ObjectA::getDate 的类型是 java.time.LocalDate

    演示:

    import java.time.LocalDate;
    
    public class Main {
        public static void main(String[] args) {
            // Test
            System.out.println(inRange(LocalDate.now()));
            System.out.println(inRange(LocalDate.of(2021, 12, 31)));
            System.out.println(inRange(LocalDate.of(2022, 2, 20)));
        }
    
        static boolean inRange(LocalDate date) {
            return !date.isAfter(LocalDate.now().plusDays(90));
        }
    }
    

    输出:

    true
    false
    false
    

    ONLINE DEMO

    通过 Trail: Date Time 了解有关 modern Date-Time API* 的更多信息。


    * 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

    【讨论】:

    • 请注意,在极端情况下,如果在流式传输时 LocalDate.now() 的输出由于是午夜而发生变化,它可能会过滤掉一些值而其他值不会。在这种情况下,可能希望在流式传输之前将 LocalDate.now() 的值存储到变量中。
    • @MCEmperor - 感谢您提供宝贵的反馈。我已经更新了答案以将其合并。
    猜你喜欢
    • 2013-11-04
    • 2013-05-19
    • 2010-10-31
    • 2016-09-22
    • 2019-02-19
    • 2019-02-14
    • 2015-01-03
    相关资源
    最近更新 更多