Current approach based on double type of product prize.
public Map<String, BigDecimal> averageProductPriceInCategory() {
return shopping.entrySet()
.stream()
.flatMap(e -> e.getValue().keySet().stream())
.collect(Collectors.groupingBy(Product::getCategory,
Collectors.averagingDouble(Product::getPrize)));
}
shopping is basically a map: Map<Client, Map<Product,Integer>>,
Below snippet can be used only for calculating the total prize of products which belong to a specified category. Not sure, how to calculate the average products prize in regards to category with using BigDecimals
Map<String, BigDecimal> totalProductPriceInEachCategory = shopping.entrySet().stream()
.flatMap(e -> e.getValue().keySet().stream())
.collect(Collectors.groupingBy(Product::getCategory,
Collectors.mapping(Product::getPrize,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
Take a look at how Collectors.averagingDouble or Collectors.averagingInt is implemented.
public static <T> Collector<T, ?, Double>
averagingInt(ToIntFunction<? super T> mapper) {
return new CollectorImpl<>(
() -> new long[2],
(a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; },
(a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID);
}
Essentially, you need a mutable accumulation type that would hold a BigDecimal which is a sum of product prices, and an int which is a number of products processed. Having that, the problem boils down to writing a simple Collector<Product, AccumulationType, BigDecimal>.
I simplified an example and removed getters/setters and an all-args constructor. Instead of a nested class ProductPriceSummary, you might use any mutable holder class for 2 elements.
class AverageProductPriceCollector implements Collector<Product, AverageProductPriceCollector.ProductPriceSummary, BigDecimal> {
static class ProductPriceSummary {
private BigDecimal sum = BigDecimal.ZERO;
private int n;
}
@Override
public Supplier<ProductPriceSummary> supplier() {
return ProductPriceSummary::new;
}
@Override
public BiConsumer<ProductPriceSummary, Product> accumulator() {
return (a, p) -> {
// if getPrize() still returns double
// a.sum = a.sum.add(BigDecimal.valueOf(p.getPrize()));
a.sum = a.sum.add(p.getPrize());
a.n += 1;
};
}
@Override
public BinaryOperator<ProductPriceSummary> combiner() {
return (a, b) -> {
ProductPriceSummary s = new ProductPriceSummary();
s.sum = a.sum.add(b.sum);
s.n = a.n + b.n;
return s;
};
}
@Override
public Function<ProductPriceSummary, BigDecimal> finisher() {
return s -> s.n == 0 ?
BigDecimal.ZERO :
s.sum.divide(BigDecimal.valueOf(s.n), RoundingMode.CEILING);
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
You can make an own collector like this:
Collector<BigDecimal, BigDecimal[], BigDecimal> avgCollector = Collector.of(
() -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO},
(pair, val) -> {
pair[0] = pair[0].add(val);
pair[1] = pair[1].add(BigDecimal.ONE);
},
(pair1, pair2) -> new BigDecimal[]{pair1[0].add(pair2[0]), pair1[1].add(pair2[1])},
(pair) -> pair[0].divide(pair[1], 2, RoundingMode.HALF_UP)
);
... and then use it:
Map<String, BigDecimal> totalProductPriceInEachCategory = shopping.values().stream()
.flatMap(e -> e.keySet().stream())
.collect(groupingBy(Product::getCategory, mapping(Product::getPrice, avgCollector)));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With