【问题标题】:Sum up fields based on another field in a stream [duplicate]基于流中的另一个字段总结字段[重复]
【发布时间】:2016-07-08 13:22:12
【问题描述】:

我有一个如下所示的对象列表:

{
    value=500
    category="GROCERY"
},
{
    value=300
    category="GROCERY"
},
{
    value=100
    category="FUEL"
},
{
    value=300
    category="SMALL APPLIANCE REPAIR"
},
{
    value=200
    category="FUEL"
}

我想将其转换为如下所示的对象列表:

{
    value=800
    category="GROCERY"
},
{
    value=300
    category="FUEL"
},
{
    value=300
    category="SMALL APPLIANCE REPAIR"
}

基本上把所有同类别的值加起来。

我应该使用 flatMap 吗?减少?我不明白这些的细微差别要弄清楚。

帮助?

编辑:

这个问题有很多重复: Is there an aggregateBy method in the stream Java 8 api?Sum attribute of object with Stream API

但在这两种情况下,最终结果都是地图,而不是列表

根据@AndrewTobilko 和@JBNizet 的回答,我使用的最终解决方案是:

List<MyClass> myClassList = list.stream()
    .collect(Collectors.groupingBy(YourClass::getCategory,
                    Collectors.summingInt(YourClass::getValue)))
    .entrySet().stream().map(e -> new MyClass(e.getKey(), e.getValue()).collect(toList());

【问题讨论】:

  • @AndrewTobilko 你的回答对我来说是正确的。你为什么删除它?
  • 是的,我抓住了它,它工作正常!你为什么删除它?唯一的问题(我可以忍受)是我得到一个 Map 返回。
  • 只需附加.entrySet().stream().map(e -&gt; new YourClass(e.getKey(), e.getValue()).collect(toList());

标签: java stream java-8 grouping


【解决方案1】:

Collectors 类提供了一个“groupingBy”,允许您对流执行“group by”操作(类似于数据库中的 GROUP BY)。假设您的对象列表是“对象”类型,以下代码应该可以工作:

Map<String, Integer> valueByCategory = myObjects.stream().collect(Collectors.groupingBy(MyObjects::getCategory, Collectors.summingInt(MyObjects::getValue)));

代码基本上按每个类别对流进行分组,并在每个组上运行一个收集器,汇总每个流元素的 getValue() 的返回值。 见https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html

【讨论】:

  • 谢谢克里斯蒂安。我对你的答案投了赞成票,但接受了 Andrew 的答案,因为他先回复(然后删除并重新发布!)
【解决方案2】:

使用Collectors 类的静态导入:

list.stream().collect(groupingBy(Class::getCategory, summingInt(Class::getValue)));

您将获得一张地图Map&lt;String, Integer&gt;Class 必须有 getValuegetCategory 方法来编写方法引用,比如

public class Class {
    private String category;
    private int value;

    public String getCategory() { return category; }
    public int getValue() { return value; }
}

【讨论】:

    【解决方案3】:

    基于reduce的方法:

    List<Obj> values = list.stream().collect(
            Collectors.groupingBy(Obj::getCategory, Collectors.reducing((a, b) -> new Obj(a.getValue() + b.getValue(), a.getCategory())))
    ).values().stream().map(Optional::get).collect(Collectors.toList());
    

    坏事是次要的stream() 调用从Optional&lt;Obj&gt; 和中间Map&lt;String, Optional&lt;Obj&gt;&gt; 对象重新映射结果。

    我可以建议使用排序的替代变体(可读性较差):

    List<Obj> values2 = list.stream()
        .sorted((o1, o2) -> o1.getCategory().compareTo(o2.getCategory()))
        .collect(
            LinkedList<Obj>::new,
            (ll, obj) -> {
                Obj last = null;
                if(!ll.isEmpty()) {
                    last = ll.getLast();
                }
    
                if (last == null || !last.getCategory().equals(obj.getCategory())) {
                    ll.add(new Obj(obj.getValue(), obj.getCategory())); //deep copy here
                } else {
                    last.setValue(last.getValue() + obj.getValue());
                }
            },
            (list1, list2) -> {
                  //for parallel execution do a simple merge join here
                  throw new RuntimeException("parallel evaluation not supported"); 
             }
        );
    

    这里我们按类别对Objs 列表进行排序,然后按顺序对其进行处理,压缩来自同一类别的连续对象。

    不幸的是,Java 中没有方法可以在不手动保留最后一个元素或元素列表的情况下执行此操作(另请参阅 Collect successive pairs from a stream

    可以在此处检查两个 sn-ps 的工作示例:https://ideone.com/p3bKV8

    【讨论】:

    • summingInt 比使用 reduce 更直接(也更高效)。
    • @BrianGoetz ...对此无可争辩。只是专注于保持数据结构并错过了转换为Map&lt;String, Integer&gt; 变体。你对第二种方法有什么看法?排序和连续求和是否比grouping+summingInt(基于排序的方法与基于哈希的方法相比)更有效?
    • @Nikolay 排序+求和只能更快,如果基于哈希的集合需要O(n) 进行查找。如果有适当的哈希函数 - 并且存在 - 那么查找需要 O(1) 来回答您的问题。
    • UPD:​​> 由于 cmets 从答案中删除了过多的限定符。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 2022-12-12
    • 2015-04-09
    • 1970-01-01
    相关资源
    最近更新 更多