【问题标题】:Best solution to collapse list of object if necessary必要时折叠对象列表的最佳解决方案
【发布时间】:2021-11-16 00:19:07
【问题描述】:

今天我试图折叠具有某些特征的对象列表。 我的想法是绝对使用 stream()。

想象一个这样的物体:

public class ObjectA {
     private Integer priority;
     private LocalDate date;
     private String string;
}

我有一个:

List<ObjectA> objects

我想折叠此列表中具有相同日期的对象。如果它们的日期在特定时间窗口内,则仅保留具有最高优先级的日期,如果优先级相等,则属性“字符串”中具有特定常量的日期将从列表中删除。

例如,这个:

 window = 1; // days
 constantString = "aab";
 [{"priority":1, "date":"2021-09-22", "aaa"},  
 {"priority":1, "date":"2021-09-23", "aab"}, 
 {"priority":1, "date":"2027-10-09", "bbb"}]

变成了这样:

[{"priority":2, "date":"2021-09-23", "aab"}, 
 {"priority":1, "date":"2027-10-09", "bbb"}]

考虑到此列表可能包含许多元素,您认为在效率方面最好的解决方案是什么。

非常感谢您的帮助!

【问题讨论】:

  • 如果窗口值为 2 是什么意思?日期为 22. Sep 和 23. Sep 的对象是否属于同一组?
  • 在提出问题后完全编辑规格的错误举措!如果两个具有相同优先级的实例都不匹配特定的常量,会发生什么?
  • 完全是厄立特里亚人。我为那个 Adriaan 感到抱歉,只是我说了很多次。为了回答你的问题,让我们假设这是不可能的,这就足够了。
  • 我建议您尝试自己解决这个问题,如果您还有其他问题,请向我们展示您的代码。

标签: java performance java-8 java-stream filtering


【解决方案1】:

我不确定这是否是最佳解决方案,但是,如果您坚持使用 Stream API,则可以通过一堆收集器来实现:

  • groupBydate 分组到Map&lt;String, List&lt;ObjectA&gt;
  • maxByList&lt;ObjectA&gt; 减少为单个对象,最高为priority(因此Comparator)。
  • 由于上述收集器导致丑陋的 Map&lt;LocalDate, Optional&lt;ObjectA&gt;&gt; 使用 collectingAndThen 使用另一个 Stream 将您需要的内容提取回 List&lt;ObjectA&gt;
final String specificConstant = "ccc";

List<ObjectA> filtered = list.stream().collect(Collectors.collectingAndThen(
    Collectors.groupingBy(
            ObjectA::getDate,
            Collectors.maxBy(Comparator
                .comparing(ObjectA::getPriority)
                .thenComparing(objA -> specificConstant.equals(objA.getString())))),
    map -> map.values().stream()
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toList())));

将结果打印到控制台的完整示例:

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

final String specificConstant = "ccc";
        
List<ObjectA> filtered = list.stream().collect(Collectors.collectingAndThen(
    Collectors.groupingBy(
            ObjectA::getDate,
            Collectors.maxBy(Comparator
                .comparing(ObjectA::getPriority)
                .thenComparing(c -> specificConstant.equals(c.getConstant())))),
    map -> map.values().stream()
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toList())));
[ObjectA(priority=1, date=2021-09-09, constant=bbb), ObjectA(priority=2, date=2021-09-22, constant=ccc)]

【讨论】:

  • 对不起,我把这个例子简单化了,它有点复杂。我编辑了这个问题。对不起。
  • 我已经编辑了我的答案。您只需要扩展Copmarator。请下次完成最小的完整示例。
  • 你是对的。我的问题是能够理解如何只考虑日期差异等于或小于“窗口”(天)值的 ObjectA,比较器可以吗?感谢您的耐心等待。
【解决方案2】:

以下是使用流的方法:

Collection<ObjectA> collapsed = values.stream()
    .collect(Collectors.toMap(
        ObjectA::getDate,
        Function.identity(),
        (a, b) -> a.priority < b.priority ? b : a))
    .values();

Collectors.toMap 方法采用以下参数:

Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction

keyMapper 确定映射键,我们使用来自 ObjectA 的日期。 valueMapper 确定映射值,这是 ObjectA 实例本身(标识函数)。 mergFunction 确定当两个值具有相同的键时会发生什么。我们在这里提供了一个 BinaryOperator,它选择具有最高优先级的元素。

编辑:当我回答这个问题时,你完全改变了问题的规格!

【讨论】:

  • 也许我应该问另一个问题来更好地指定案例。很抱歉。你的答案是正确的!
【解决方案3】:

您可以在O(n) 时间内执行此操作,n 是列表中元素的数量。

将列表转换为以键为日期、值为优先级的 HashMap。然后,当您点击与您记录的元素具有相同日期的元素时,如果新值具有更高的优先级,则可以替换该日期键的值。

然后,您可以根据需要将 HashMap 转换回列表。

【讨论】:

  • 对不起,我把这个例子简单化了,它有点复杂。我编辑了这个问题。对不起。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-12
  • 1970-01-01
  • 2014-07-11
  • 2014-02-11
  • 2015-02-27
  • 2023-03-13
相关资源
最近更新 更多