【问题标题】:Java 8 Steam Group By on two properties and get average of third property [duplicate]Java 8 Steam Group By在两个属性上并获得第三个属性的平均值[重复]
【发布时间】:2019-05-06 18:01:35
【问题描述】:

我在一个 ArrayList 中有以下类对象:

new Pojo("Football","M",1000)
new Pojo("Football","M",1000)
new Pojo("Cricket","F",500)
new Pojo("Cricket","F",1500)
new Pojo("Cricket","M",500)
new Pojo("Cricket","M",500)

我想获得“体育”相对于“性别”的平均分。 我遇到了 Java 8 Streams,在这里使用它似乎很好,但我不知道如何实现它。

我试过如下:

Map<Object,Long> mp = pojoList.stream().collect(Collectors.groupingBy(p -> p.sport,Collectors.counting()));

效果很好,给了我一个运动计数,但我想再应用一个性别过滤器并获得平均分。

我想要的输出:

new PojoOutput("Football","M",1000)
new PojoOutput("Cricket","F",1000)
new PojoOutput("Cricket","M",500)

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    您可以通过使用 2 个 groupingBy 调用来分组到地图,其中一个是另一个的下游:

    Map<String, Map<String, Double>> res = pojos.stream()
        .collect(Collectors.groupingBy(Pojo::getSport,
                Collectors.groupingBy(Pojo::getGender, 
                                      Collectors.averagingDouble(i -> i.getPoints()))));
    

    这会产生{Cricket={F=1000.0, M=500.0}, Football={M=1000.0}},您可以使用以下方式轻松地将其转换为Pojo 实例:

    List<Pojo> groups = result.entrySet()
            .stream()
            .flatMap(sport -> sport.getValue()
                            .entrySet().stream()
                            .map(gender -> new Pojo(sport.getKey(), 
                                                    gender.getKey(), 
                                                    gender.getValue())))
            .collect(Collectors.toList());
    

    生成toString的结果:

    [Pojo [sport=Cricket, gender=F, points=1000.0], 
     Pojo [sport=Cricket, gender=M, points=500.0], 
     Pojo [sport=Football, gender=M, points=1000.0]]
    

    【讨论】:

    • @ernest_k- 感谢您的回复。它就像一个魅力。节省了我的时间。
    【解决方案2】:

    您可以使用SimpleEntry 对您的数据进行分组。之后,您可以将结果映射回您的PojoOutput

    List<PojoOutput> mp = pojoList.stream()
            .collect(Collectors.groupingBy(x -> new AbstractMap.SimpleEntry<>(x.getSport(), x.getGender()), Collectors.averagingInt(Pojo::getScore)))
            .entrySet().stream()
            .map(e -> new PojoOutput(e.getKey().getKey(), e.getKey().getValue(), e.getValue()))
            .collect(Collectors.toList());
    

    您可以直接在 group by 子句中使用 PojoOutput 稍微改进该解决方案。因此PojoOutput需要一个以Pojo为属性的构造函数,并根据字段实现equals()hashCode():例如类似的东西:

    private static class PojoOutput {
        private String sport;
        private String gender;
        private double average;
    
        public PojoOutput(Pojo pojo) {
            this.sport = pojo.getSport();
            this.gender = pojo.getGender();
        }
    
        public void setAverage(double average) {
            this.average = average;
        }
    
        // equals, hashCode and getter
    }
    

    现在你可以像这样使用那个类了:

    List<PojoOutput> mp = pojoList.stream()
            .collect(Collectors.groupingBy(PojoOutput::new, Collectors.averagingInt(Pojo::getScore)))
            .entrySet().stream()
            .peek(p -> p.getKey().setAverage(p.getValue()))
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    

    两种情况的结果都是:

    PojoOutput[sport='Cricket', gender='M', average=500.0]
    PojoOutput[sport='Football', gender='M', average=1000.0]
    PojoOutput[sport='Cricket', gender='F', average=1000.0]
    

    【讨论】:

    • 除了x -&gt; new AbstractMap.SimpleEntry&lt;&gt;(x.getSport(), x.getGender()),您还可以使用x -&gt; Arrays.asList(x.getSport(), x.getGender()),它可以在需要时轻松扩展到两个以上的值。
    • @Holger 是的,但是您可以在SimpleEntry 中使用不同的类型(例如字符串和整数)。
    • 嗯,是的,这使得PojoOutput 的后续构造变得更加容易。列表也可以,但可能需要类型转换。
    【解决方案3】:

    您可以使用Arrays::asList 作为要分组的密钥:

     pojoList.stream() 
             .collect(Collectors.groupingBy(
                         x -> Arrays.asList(x.getSport(), x.getGender()),
                         Collectors.averagingInt(Pojo::getScore)));
    

    或者,如果您有 PojoOutput,只需将 x -&gt; Arrays.asList(x.getSport(), x.getGender()) 替换为该构造函数即可。

    【讨论】:

      【解决方案4】:

      您希望地图的键是 [运动,性别]。所以你需要创建一个类SportAndGenderKey包含这两个属性和一个适当的equals和hashCode方法,然后你需要通过SportAndGenderKey对你的对象进行分组。

      然后您想使用平均收集器而不是计数收集器。

      注意:键可能只是一个包含两个属性的列表,但我发现它不如专用键类清晰。

      【讨论】:

        【解决方案5】:

        正如@JB Nizet 所说,您可以遵循以下代码:

        list.stream()
                    .collect(Collectors.toMap(p -> 
                        new PojoOutput(p.getSport(), p.getGender()),Pojo::getPoint, (v1, v2) -> (v1 + v2) / 2))
                    .entrySet()
                    .stream()
                    .map(entry -> entry.getKey().setAverage(entry.getValue()))
                    .collect(Collectors.toList())
        

        只需基于两个属性(sport & geneder)覆盖PojoOutput 类中的equalshashCode 方法

        还有:

        public PojoOutput setAverage(Integer average) {
            this.average = average;
            return this;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-02-15
          • 2017-06-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多