当前位置: 首页 > 知识库问答 >
问题:

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

程凯定
2023-03-14

我的情况是这样的:

@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天的物体。

要选择两个赢家中的哪一个,请选中:

  • 首先谁具有最大的优先A属性,如果他们是相等的然后
  • 勾选"priorityB",如果这也一样
  • 检查较小的日期,如果这也是相同的然后
  • id较小的那个。

我的执行情况如下:

    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天的对象

通过适当的实施,我应该有如下示例:

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一起分组,为什么?”

  • 在这种情况下,我拿第一对就足够了,第二对就失去意义了。最终目标很简单,就是没有日期接近90天的对象

另一个例子:

  • A=1优先级(2022年1月1日)

A-B取B,B-C取B,所以有了3,我只取B。

对于这种情况,使用stream、collect和Collector的解决方案可能不是最好的?

编辑:这是解决问题的另一种方法,我如何改进它?

// 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. 

谢谢大家能给的帮助。

共有3个答案

童花蜂
2023-03-14

尝试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();
林承悦
2023-03-14

在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())));
杨成礼
2023-03-14

如果我理解正确的话,这是一个两步排序问题。首先,您需要按日期对列表进行排序,以便能够在90天的时间间隔内对项目列表进行分组,然后根据您的优先级要求对组进行重新排序。

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

示例代码,我用其他列表元素补充了该代码:

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

    //https://stackoverflow.com/questions/35872236/sorting-lists-after-groupingby
    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;
    }
}
 类似资料:
  • 问题内容: 我有一列想要四舍五入到查询中的下一个较低的10分钟间隔(请参见下面的示例)。 我设法通过截断秒数然后减去分钟的最后一位来做到这一点。 结果如下: 01.01.2010 10: 00:00 听听01.01.2010 10: 00:00 01.01.2010 10: 05:00 听听01.01.2010 10: 00:00 01.01.2010 10: 9时59 听听01.01.2010

  • 问题内容: 我正在使用PHPMyadmin,并使用PHP将值放入数据库中。我使用时间戳存储产品的到期日期,如下所示,例如: 我要选择所有到期日期等于今天的日期加上8天的日期(例如上面的那一天) 我也想在一个单独的页面中选择所有过期日期等于今天日期+ 2周的内容,如果有人可以帮助我,将不胜感激! 问题答案: 您可以使用以下查询来做到这一点: 您可以使用过去的日期。

  • 问题内容: 我是bash的新手,我的任务是删除所有30天以上的文件,我可以根据文件名来解决这个问题 。 我知道我可以在包含文件的文件夹中列出所有文件。我知道我可以获取今天的日期,并可以对其进行配置以匹配文件格式 我知道我可以使用删除文件。 我如何将所有这些结合到一个bash脚本中,该脚本从今天起删除30天以上的文件? 在伪python代码中,我想它看起来像: 问题答案: 我绝不是系统管理员,但您可

  • 问题内容: 我正在编写一个利用JavaScript超时和间隔来更新页面的应用程序。有没有办法查看设置了多少间隔?我想确保不会因设置数百个间隔而意外杀死浏览器。 这甚至是个问题吗? 问题答案: 我不认为有一种方法来枚举活动的定时器,但是你可以重写,并与自己的实现其做一些跟踪,然后调用原件替换它们。 当然,您不一定总是调用,但这至少可以为您提供某种方式来跟踪运行时发生的情况。

  • 问题内容: 我正在使用MySQL,并且具有下表: 我希望能够生成这样的报告,其中在过去4周内完成了各个周期: 或最近3个月内: 有什么想法可以进行选择查询以生成等效的日期范围和点击次数吗? 问题答案:

  • 我正在尝试将时间戳的时间为05:59:59 AM或以下的所有数据行转换为上一个日期。因此,例如“2018-12-08 05:05:00”将是“2018-12-07”,“2018-11-06 03:02:00”将是“2018-11-05”。 时间戳的格式为“yyyy-mm-dd hh:mm:ss”,并存储为varchar “从AWS Athena客户端引发错误。syntax_error:第1:17行