按范围日期分组

GroupingBy a range dates

我遇到这样的情况:

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())));

并且它在相同的日期上完美运行。 我只需要将某个日期的项目分组 window:

Integer daysWindow = 90;

所以我希望在 window 中的日期发生所有事情。 这怎么可能?

感谢大家!

您可以在开始时应用过滤器,如下所示,保持其余部分不变。

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

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


* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and

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

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]}

并且当使用不同的 window、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]}