将相隔不超过 90 天的所有日期分组

Group all dates that are no more than 90 days apart

我遇到这样的情况:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ObjectA {
     int id;
     LocalDate date;
     int priorityA;
     int priorityB;
}

List<ObjectA> list = List.of(
        new ObjectA(1, LocalDate.parse("2021-09-22"), 2, 1),
        new ObjectA(2, LocalDate.parse("2021-09-22"), 2, 1),
        new ObjectA(3, LocalDate.parse("2022-09-22"), 2, 1),
        new ObjectA(4, LocalDate.parse("2022-10-22"), 2, 1)
);

我必须折叠这些相隔不超过90天的对象。

要选择两个获胜者,请选中:

我的实现如下:

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

但是对于这个实现我有两个问题:

  1. 我只对具有相同日期的对象进行分组,而我必须能够对那些在相同日期范围内的对象进行分组,那些日期相隔不超过 90 天的对象.
  2. 我无法合并我需要的 maxBy 优先级(priorityA,priorityB)和数据和 ID 的 minBy

通过适当的实施,我应该有类似的东西 示例:

ObjectA(1, LocalDate.parse("2021-09-22"), 2, 1)
ObjectA(2, LocalDate.parse("2021-09-22"), 2, 1)
ObjectA(3, LocalDate.parse("2022-09-22"), 2, 1)
ObjectA(4, LocalDate.parse("2022-10-22"), 2, 1)
   that became
ObjectA(1, LocalDate.parse("2021-09-22"), 2, 1)
ObjectA(3, LocalDate.parse("2022-09-22"), 2, 1)

因为它们在第一对中具有相同的优先级和日期,所以我选择了 ID 最小的那个 - 而在第二对中它们具有相同的优先级但日期不同所以我选择了一个最小日期。

有好心人能帮帮我吗?我已经在论坛上问过其他类似的问题,但是我没有很好地解释自己,而且答案再正确也不令人满意。

编辑: “如果你有像 A=2022-01-01、B=2022-03-01、C=2022-05-01 这样的日期怎么办?A 和 B 在 90 天内,B 和 C 在 90 天内,但是 A和 C 超出了阈值。您是将 A 和 B 组合在一起还是将 B 和 C 组合在一起,为什么?

另一个例子:

A-B拿B,B-C拿B。所以那个3我只拿B。

也许流、收集和收集器的解决方案对于这种情况不是最好的?

编辑:这是另一种解决方法,我该如何改进?

// added a boolean in ObjectA -> "dropped" default false

for(int i = 0; i < list.size(); i++) {
            for (int j = 1; j < list.size(); j++) {
                if(list.get(i) == list.get(j) && (list.get(i).dropped || list.get(j).dropped))
                    continue;

                long diff = ChronoUnit.DAYS.between(list.get(i).getDate(), list.get(j).getDate());
                if(diff <= daysWindow && diff >= 0 && list.get(i) != list.get(j)) {
                    if (list.get(i).getPriorityA() != list.get(j).getPriorityA()) {
                        if (list.get(i).getPriorityA() > list.get(j).getPriorityA()) {
                            list.get(j).setDropped(true);
                        } else {
                            list.get(i).setDropped(true);
                        }
                    } else if (list.get(i).getPriorityB() != list.get(j).getPriorityB()) {
                        if (list.get(i).getPriorityB() > list.get(j).getPriorityB()) {
                            list.get(j).setDropped(true);
                        } else {
                            list.get(i).setDropped(true);
                        }
                    } else if (list.get(i).getDate().compareTo(list.get(j).getDate()) != 0){
                        if (list.get(i).getDate().compareTo(list.get(j).getDate()) > 0) {
                            list.get(i).setDropped(true);
                        } else {
                            list.get(j).setDropped(true);
                        }
                    } else {
                        if (list.get(i).getId()>list.get(j).getId()) {
                            list.get(i).setDropped(true);
                        } else {
                            list.get(j).setDropped(true);
                        }
                    }
                }
            }
        }

 // then i filter for take all objectA with dropped false. 

感谢大家的帮助。

如果我没理解错的话,那是你遇到的两步排序问题。首先,您需要按日期对列表进行排序,以便能够以 90 天的间隔对项目列表进行分组,然后根据您的优先级要求重新对组进行排序。

我会首先按日期对列表进行排序,并记住原子引用中第一个对象的日期。然后可以使用此引用来形成组,方法是查看每次迭代以查看当前对象的日期是否小于 90 天,如果是,则它属于当前组,否则更新引用并形成新组新日期作为关键。然后对每个组的元素进行排序,并从每个组中取出第一个元素。要查看如何对 groupingBy 收集器产生的组进行排序,请参阅此 post .

示例代码,我补充了额外的列表元素:

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

import static java.time.temporal.ChronoUnit.DAYS;

public class Example {
    public static void main(String args[]) {

        List<ObjectA> list = List.of(
                new ObjectA(1, LocalDate.parse("2021-09-22"), 2, 1),
                new ObjectA(2, LocalDate.parse("2021-09-22"), 2, 1),

                new ObjectA(3, LocalDate.parse("2022-09-22"), 2, 1),
                new ObjectA(4, LocalDate.parse("2022-10-22"), 2, 1),

                new ObjectA(10, LocalDate.parse("2025-09-22"), 2, 1),
                new ObjectA(20, LocalDate.parse("2025-09-22"), 2, 1),
                new ObjectA(30, LocalDate.parse("2025-09-23"), 2, 1),

                new ObjectA(40, LocalDate.parse("2029-09-22"), 2, 1),
                new ObjectA(13, LocalDate.parse("2029-09-22"), 2, 1),
                new ObjectA(23, LocalDate.parse("2029-09-22"), 2, 1),
                new ObjectA(33, LocalDate.parse("2029-09-22"), 2, 1),
                new ObjectA(4, LocalDate.parse("2029-09-22"), 2, 1)
        );



        // for a better overview single comparators which are used as own variables
        Comparator<ObjectA> byPrioA = Comparator.comparing(ObjectA::getPriorityA, Comparator.reverseOrder());
        Comparator<ObjectA> byPrioB = Comparator.comparing(ObjectA::getPriorityB, Comparator.reverseOrder());
        Comparator<ObjectA> byDate  = Comparator.comparing(ObjectA::getDate);
        Comparator<ObjectA> byId    = Comparator.comparing(ObjectA::getId);

        //first who has the largest "priorityA" attribute, if they are equal then
        //check "priorityB", if this is also the same
        //check the lesser date and if that is also the same then
        //the one with the lesser id.
        Comparator<ObjectA> combined = byPrioA.thenComparing(byPrioB).thenComparing(byDate).thenComparing(byId);

        //sort list by date
        List<ObjectA> sortedByDate = list.stream().sorted(byDate).collect(Collectors.toList());

        //store first date for first group key
        AtomicReference<LocalDate> ar = new AtomicReference<>(sortedByDate.get(0).getDate());

        sortedByDate.stream().collect(Collectors.groupingBy(
                d -> DAYS.between(ar.get(), d.getDate()) < 90 ? ar.get() : ar.accumulateAndGet(d.getDate(), (u,v) ->v),
                LinkedHashMap::new,
                Collectors.collectingAndThen(toSortedList(combined), l -> l.get(0))))
                .values()
                .forEach(System.out::println);
    }

    //
    static <T> Collector<T,?,List<T>> toSortedList(Comparator<? super T> c) {
        return Collectors.collectingAndThen(
                Collectors.toCollection(()->new TreeSet<>(c)), ArrayList::new);
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    public static class ObjectA {
        int id;
        LocalDate date;
        int priorityA;
        int priorityB;
    }
}

在 90 天内任意选择一个日期并以此为依据分组是否可行?这似乎是一个简单的解决方案:

    List<ObjectA> collapsed = list.stream()
            .collect(Collectors.collectingAndThen(
            Collectors.groupingBy(
                    x -> LocalDate.ofEpochDay(x.getDate().toEpochDay() % 90),
                    Collectors.maxBy(Comparator
                            .comparing(ObjectA::getPriorityA)
                            .thenComparing(ObjectA::getPriorityB)
                            .thenComparing(ObjectA::getDate)
                            .thenComparing(ObjectA::getId))),
            map -> map.values().stream()
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .collect(Collectors.toList())));

尝试 StreamEx.collapse:

final Comparator<ObjectA> cmp = Comparator.comparing(ObjectA::getPriorityA)
.thenComparing(ObjectA::getPriorityB)
.reversed()
.thenComparing(ObjectA::getDate)
.thenComparing(ObjectA::getId);

List<ObjectA> collapsed = StreamEx.of(list)
    .collapse((a, b) -> ChronoUnit.DAYS.between(a.getDate(), b.getDate()) <= 90,
        (a, b) -> cmp.compare(a, b) <= 0 ? a : b)
    .toList();