有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

arraylist如何使用JAVA流从对象列表中查找平均值

我有两门课,账单和费用如下

class Bill {

    private String id;
    private List<Charge> charges; 
    // Getters Setters Constructors etc..

}
class Charge{

    private String typeId;
    private double a;
    private double b;
    // Getters Setters Constructors etc..
}
List<Bill> bills  = new ArrayList<>();

Bill b1 = new Bill();
b1.setId("1");
List<Charge> charges = new ArrayList<>();
charges.add(new Charge("type-1",20,30));
charges.add(new Charge("type-2",30,30));
b1.setCharges(charges);

Bill b2 = new Bill();
b2.setId("2");
List<Charge> charges2 = new ArrayList<>();
charges2.add(new Charge("type-1",30,40));
charges2.add(new Charge("type-2",40,40));
b2.setCharges(charges2);

现在我有了一个方法, 此方法应基于typeId平均费用,并且每个typeId只返回一笔费用

public Bill average(List<Bill> bills){
...
}

我希望这个方法能返回如下的账单

Bill{
    id:null,
    charges:[
        {
            typeId:"type-1",
            a:25,
            b:35
        },
        {
            typeId:"type-2",
            a:35,
            b:35
        }
    ]
}

这可以通过for或forEach循环实现,但我希望解决这个Streams api


共 (4) 个答案

  1. # 1 楼答案

    这应该行得通,不过看起来不太漂亮:

    public Bill average(List<Bill> bills) {
        List<Charge> avgCharges = bills.stream().flatMap(b -> b.getCharges().stream())
            .collect(Collectors.groupingBy(Charge::getTypeId))
            .entrySet().stream()
            .collect(Collectors.toMap(x -> {
                double avgA = x.getValue().stream().mapToDouble(Charge::getA).average().getAsDouble();
                double avgB = x.getValue().stream().mapToDouble(Charge::getB).average().getAsDouble();
                return new Charge(x.getKey(), avgA, avgB);
            }, Map.Entry::getValue))
            .keySet().stream().collect(Collectors.toList());
    
        return new Bill("avgCharges", avgCharges);
    }
    

    测试片段:

    Bill avg = average(Arrays.asList(b1, b2));
    
    avg.getCharges().stream()
    .forEach(c -> System.out.println(c.getTypeId() + "-> a=" + c.getA() + ", b=" + c.getB()));
    
    

    提供以下输出:

    type-1-> a=25.0, b=35.0
    type-2-> a=35.0, b=35.0
    
  2. # 2 楼答案

    public Bill average(List<Bill> bills) {
        final List<Charge> charges = bills.stream()
                .flatMap(x -> x.getCharges().stream())
                .collect(Collectors.groupingBy(Charge::getTypeId))
                .entrySet().stream()
                .map(x -> new Charge(
                        x.getKey(),
                        x.getValue().stream().mapToDouble(Charge::getA).average().getAsDouble(),
                        x.getValue().stream().mapToDouble(Charge::getB).average().getAsDouble()))
                .collect(Collectors.toList());
        return new Bill(null, charges);
    }
    

    或者

    public Bill average(List<Bill> bills) {
        return bills.stream()
                .flatMap(x -> x.getCharges().stream())
                .collect(Collectors.collectingAndThen(Collectors.groupingBy(Charge::getTypeId),
                        x -> {
                            final List<Charge> charges = x.entrySet().stream()
                                    .map(y -> new Charge(
                                            y.getKey(),
                                            y.getValue().stream().mapToDouble(Charge::getA).average().getAsDouble(),
                                            y.getValue().stream().mapToDouble(Charge::getB).average().getAsDouble()))
                                    .collect(Collectors.toList());
                            return new Bill(null, charges);
                        }));
    }
    
  3. # 3 楼答案

    public static Bill average(List<Bill> bills) {
        final List<Charge> charges = bills.stream()
                .flatMap(x -> x.getCharges().stream())
                .collect(Collectors.collectingAndThen(
                        Collectors.groupingBy(
                                Charge::getTypeId,
                                billInfoToAverage()
                        ),
                        x -> new ArrayList<>(x.values())
                ));
        return new Bill(null, charges);
    }
    
    
    public static Collector<Charge, BillInfoAccumulator, Charge> billInfoToAverage() {
        return Collector.of(
                BillInfoAccumulator::new,
                BillInfoAccumulator::add,
                BillInfoAccumulator::combine,
                BillInfoAccumulator::average
        );
    }
    
    
    class BillInfoAccumulator {
        private String typeId;
        private final DoubleSummaryStatistics aStats = new DoubleSummaryStatistics();
        private final DoubleSummaryStatistics bStats = new DoubleSummaryStatistics();
    
        public void add(Charge charge) {
            typeId = charge.getTypeId();
            aStats.accept(charge.getA());
            bStats.accept(charge.getB());
        }
    
        public BillInfoAccumulator combine(BillInfoAccumulator accumulator) {
            aStats.combine(accumulator.aStats);
            bStats.combine(accumulator.bStats);
            return this;
        }
    
        public Charge average() {
            return new Charge(typeId, aStats.getAverage(), bStats.getAverage());
        }
    }
    
  4. # 4 楼答案

    一个非常简单的解决方案。当然,操作的数量可以减少到一半,但这使理解变得更简单

    1. 按类型收取每张账单的费用。由于每个账单都有费用列表,我们需要使用flatMap提取费用并将其收集到一个列表中,然后filter按类型过滤
    2. 根据类型找出a和b的平均值
    3. 创建一个新的账单对象并返回相同的账单对象

          List<Charge> t1Charges = bills.stream()
                                        .flatMap(b -> b.charges.stream())
                                        .filter(c -> c.typeId == "type-1")
                                        .collect(Collectors.toList());
          List<Charge> t2Charges = bills.stream()
                                        .flatMap(b -> b.charges.stream())
                                        .filter(c -> c.typeId == "type-2")
                                        .collect(Collectors.toList());
      
          Double t1a = t1Charges.stream()
                                .mapToDouble(x -> x.a)
                                .average()
                                .getAsDouble();
          Double t1b = t1Charges.stream()
                                .mapToDouble(x -> x.b)
                                .average()
                                .getAsDouble();
      
          final Double t2a = t2Charges.stream()
                                      .mapToDouble(x -> x.a)
                                      .average()
                                      .getAsDouble();
          final Double t2b = t2Charges.stream()
                                      .mapToDouble(x -> x.b)
                                      .average()
                                      .getAsDouble();
      
          final Bill av = new Bill();
          av.charges.add(new Charge("type-1",t1a,t1b));
          av.charges.add(new Charge("type-2",t2a,t2b));
      
          return av;