【问题标题】:How to find average from a list of objects using JAVA streams如何使用 JAVA 流从对象列表中查找平均值
【发布时间】:2020-05-19 15:03:42
【问题描述】:

我有两个类 Bill 和 Charge,如下所示。

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 平均 Charges,并且每个 typeId 仅返回一个 Charge

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

【问题讨论】:

  • 这可以使用 for 或 forEach 循环实现,请分享。

标签: java arraylist collections java-stream


【解决方案1】:
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());
    }
}

【讨论】:

    【解决方案2】:

    这应该可以,虽然它看起来不太漂亮:

    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);
    }
    

    测试sn-p:

    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
    

    【讨论】:

      【解决方案3】:
      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);
                          }));
      }
      

      【讨论】:

        【解决方案4】:

        一个非常天真和简单的解决方案。当然操作次数可以减半,但这更容易理解。

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

              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;
          

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-09-25
          • 1970-01-01
          • 1970-01-01
          • 2012-02-20
          • 1970-01-01
          相关资源
          最近更新 更多