【问题标题】:Group by a filed and count the non null fields in java 8 [duplicate]按字段分组并计算java 8中的非空字段[重复]
【发布时间】:2021-08-18 14:09:59
【问题描述】:

我尝试使用 java 8 功能。

我有课

@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
    private String pId;
    private String uId;
    private String price;
}

我有一个列表,我尝试按pId 分组并计算非空uIdprice。示例:

List<User> list =
Arrays.asList(
    new User ("p1", "u1", null),
    new User ("p1", "u2", "a"),
    new User ("p2", null, "b"),
    new User ("p2", null, "c"),
    new User ("p3", "u4", "d")
);

我的预期输出是

[
    { pId:"p1", uCount:2, priceCount:1 },
    { pId:"p2", uCount:0, priceCount:2 },
    { pId:"p3", uCount:1, priceCount:1 }
]

我尝试如下

Map<String, Map<Object, Long>> collect =
    list.stream()
        .collect(
            Collectors.groupingBy(
                User ::getPId,
                Collectors.groupingBy(f -> f.getUId(), Collectors.counting())));

我的最终映射类是

@Data
@NoArgsConstructor
@AllArgsConstructor
class Stat {
    private String pId;
    private Integer uCount;
    private Integer priceCount;
}

由于我是 java 新手,我面临着完成它的困难,我尽力了。是否可以删除空字段并计数?

【问题讨论】:

标签: java collections java-8 java-stream


【解决方案1】:

Java 12+ 解决方案

通过使用teeing 收集器和filtering,您可以这样做:

Map<String, Detail> result = list.stream()
       .collect(Collectors.groupingBy(User::getpId, Collectors.teeing(
           Collectors.filtering(u -> u.getuId() != null, Collectors.counting()),
           Collectors.filtering(u -> u.getPrice() != null, Collectors.counting()),
           (uCount, priceCount) -> new Detail(uCount, priceCount)
        )));

class Detail{
  private long uCount;
  private long priceCount;
}

输出:

{ p1=Detail{uCount=2, priceCount=1}, p2=Detail{uCount=0, priceCount=2}, p3=Detail{uCount=1, priceCount=1} }

【讨论】:

  • 为什么不在问题中使用Stat 类而不是Detail
  • @Naman, Stat 对象包括 pId 因为它需要更多的过程来创建它。我认为使用Detail 很简单。
【解决方案2】:

您可以尝试以下解决方案吗?它将提供正确的输出:

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class Main {

    public static void main(String[] args) {
        List<User> list =
                Arrays.asList(
                    new User ("p1", "u1", null),
                    new User ("p1", "u2", "a"),
                    new User ("p2", null, "b"),
                    new User ("p2", null, "c"),
                    new User ("p3", "u4", "d")
                );

        Set<Stat> set = list.stream().map(s -> new Stat(s.getPId(), 0, 0)).collect(Collectors.toSet());
        set = set.stream().map(s -> updateCounts(list, s.getPId())).collect(Collectors.toSet());

        System.out.println(set);
    }

    public static Stat updateCounts(List<User> list, String pId) {
        Long uCount = list.stream().filter(s -> pId.equals(s.getPId()) && Objects.nonNull(s.getUId()))
            .collect(Collectors.counting());

        Long priceCount = list.stream().filter(s -> pId.equals(s.getPId()) && Objects.nonNull(s.getPrice()))
                .collect(Collectors.counting());
        return new Stat(pId, Integer.valueOf(uCount+""), Integer.valueOf(priceCount+""));
    }
}

【讨论】:

    【解决方案3】:

    我会建议以下解决方案。它对每个组使用归约操作,并且需要对 Stat 类进行一些添加,以使代码看起来有些整洁。

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class Stat {
      private String pId;
      private Integer uCount;
      private Integer priceCount;
    
      public static Stat init() {
        return new Stat(null, Integer.valueOf(0), Integer.valueOf(0));
      }
    
      public void incValue(Stat s) {
        this.uCount += (s.uCount !=null) ? s.uCount : 0;
        this.priceCount+= (s.priceCount!=null) ? s.priceCount: 0;
      }
    
      public static Stat mapToStat(User user) {
        Stat s = new Stat(user.getPId(), 0, 0);
        if(user.getUid() != null) {
          s.uCount++;
        }
        if(user.getPrice() != null) {
          s.priceCount++;
        }
      }
    }
    

    现在是流操作:

    return list.stream() // List<Stat> returned
               .collect(Collectors.groupingBy(User::getPId, Collectors.reducing(new Stat(), Stat::mapToStat, (s1, s2) -> {
                    // You could move the below part as well to a method in Stat
                    Stat s = Stat.init();
                    s.setPId(s1.getPId());
                    s.incValue(s1);
                    s.incValue(s2);
                    if (s1.getPId() == null) {
                        s.setPId(s2.getPId());
                    }
                    return s;
                })))
                .values()
                .stream()
                .collect(Collectors.toList());
    
    

    我会把优化/清理部分留给你:-)

    另外,请查看此问题/答案。这也是一个非常相似但略有不同的场景。 https://stackoverflow.com/a/67240321/7804477

    【讨论】:

    【解决方案4】:

    你可以使用reducing操作:

    import static java.util.stream.Collectors.*;
    
    Function<User, Stat> getStatFromUser = 
        u -> new Stat(u.getpId(), 
                      u.getuId() == null ? 0 : 1,
                      u.getPrice() == null ? 0 : 1);
                            
    BinaryOperator<Stat> addStats = 
        (o1, o2) -> new Stat(// Imp to getpId from o2 as for the first element pId of o1 (identity element) would be ""
                              o2.getpId(), 
                              o1.getuCount() + o2.getuCount(),
                              o1.getPriceCount() + o2.getPriceCount());
    
    Map<String, Stat> result = 
        list.stream().collect(
                        groupingBy(User::getpId,
                                   reducing(
                                      new Stat(""),
                                      u -> statFromUser.apply(u),
                                      (o1, o2) -> addStats.apply(o1, o2))));
    

    对于有问题的测试用例,输出应如下所示:

    {
     p1= {pId=p1, uCount=2, priceCount=1}, 
     p2= {pId=p2, uCount=0, priceCount=2}, 
     p3= {pId=p3, uCount=1, priceCount=1}
    }
    

    要获得预期的输出,您可以在 groupingBy 上调用 collectingAndThen

    Object result = 
        list.stream()
              .collect(
                 collectingAndThen(
                     groupingBy(User::getpId,
                                reducing(new Output(""),
                                         u -> outputFromUser.apply(u),
                                         (o1, o2) -> addOuputs.apply(o1, o2))),
                     m -> m.values()));
    

    上述构造的输出是:

    [
       Output {pId=p1, uCount=2, priceCount=1}, 
       Output {pId=p2, uCount=0, priceCount=2}, 
       Output {pId=p3, uCount=1, priceCount=1}
    ]
    

    编辑 1:

    另一种解决方案是使用toMap:

    Object result = 
         list.stream()
               .collect(
                   collectingAndThen(
                        toMap(User::getpId, 
                              u -> outputFromUser.apply(u), 
                              (o1, o2) -> addOuputs.apply(o1, o2)),
                        m -> m.values()));
    

    【讨论】:

      猜你喜欢
      • 2012-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-31
      • 2011-08-29
      • 1970-01-01
      • 2017-01-08
      • 2017-07-27
      相关资源
      最近更新 更多