简体   繁体   中英

How to find average from a list of objects using JAVA streams

I have two classes Bill and Charge as follows.

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

now i have a method, this method should average Charges based on typeId and return only one Charge per typeId

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

i want this method to return a bill like following

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

this can be achieved using for or forEach loops but I am looking to resolve this Streams api

This should work, though it does not look too beautful:

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

Testing snippet:

Bill avg = average(Arrays.asList(b1, b2));

avg.getCharges().stream()
.forEach(c -> System.out.println(c.getTypeId() + "-> a=" + c.getA() + ", b=" + c.getB()));

provides the following output:

type-1-> a=25.0, b=35.0
type-2-> a=35.0, b=35.0
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());
    }
}
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);
}

Or

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

A very naive and simple solution. Of course the number of operations can be reduced to half but this makes it simpler to understand.

  1. Collect charges from each bills according to types. Since each bill has list of charges, we need to use flatMap for extracting charges and collecting them all into one list and filter to filter by type.
  2. Find average of a and b according to type
  3. Create a new Bill object and return the same

     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 -> xa).average().getAsDouble(); Double t1b = t1Charges.stream().mapToDouble(x -> xb).average().getAsDouble(); final Double t2a = t2Charges.stream().mapToDouble(x -> xa).average().getAsDouble(); final Double t2b = t2Charges.stream().mapToDouble(x -> xb).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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM