java collections java-8 bigdecimal java-stream

java - ¿Cómo promediar BigDecimals usando Streams?



stream().map java (4)

Alternativamente, puede utilizar esta implementación de Collector:

class BigDecimalAverageCollector implements Collector<BigDecimal, BigDecimalAccumulator, BigDecimal> { @Override public Supplier<BigDecimalAccumulator> supplier() { return BigDecimalAccumulator::new; } @Override public BiConsumer<BigDecimalAccumulator, BigDecimal> accumulator() { return BigDecimalAccumulator::add; } @Override public BinaryOperator<BigDecimalAccumulator> combiner() { return BigDecimalAccumulator::combine; } @Override public Function<BigDecimalAccumulator, BigDecimal> finisher() { return BigDecimalAccumulator::getAverage; } @Override public Set<Characteristics> characteristics() { return Collections.emptySet(); } @NoArgsConstructor @AllArgsConstructor static class BigDecimalAccumulator { @Getter private BigDecimal sum = BigDecimal.ZERO; @Getter private BigDecimal count = BigDecimal.ZERO; BigDecimal getAverage() { return BigDecimal.ZERO.compareTo(count) == 0 ? BigDecimal.ZERO : sum.divide(count, 2, BigDecimal.ROUND_HALF_UP); } BigDecimalAccumulator combine(BigDecimalAccumulator another) { return new BigDecimalAccumulator( sum.add(another.getSum()), count.add(another.getCount()) ); } void add(BigDecimal successRate) { count = count.add(BigDecimal.ONE); sum = sum.add(successRate); } } }

Y úsalo así:

BigDecimal mean = bigDecimals.stream().collect(new BigDecimalAverageCollector());

Nota: el ejemplo utiliza las anotaciones del Proyecto Lombok para acortar el código del pegamento.

Estoy deseando tomar el siguiente método:

public BigDecimal mean(List<BigDecimal> bigDecimals, RoundingMode roundingMode) { BigDecimal sum = BigDecimal.ZERO; int count=0; for(BigDecimal bigDecimal : bigDecimals) { if(null != bigDecimal) { sum = sum.add(bigDecimal); count++; } } return sum.divide(new BigDecimal(count), roundingMode); }

y actualizarlo utilizando la API de Streams. Esto es lo que tengo hasta ahora:

public BigDecimal average(List<BigDecimal> bigDecimals, RoundingMode roundingMode) { BigDecimal sum = bigDecimals.stream() .map(Objects::requireNonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); long count = bigDecimals.stream().filter(Objects::nonNull).count(); return sum.divide(new BigDecimal(count), roundingMode); }

¿Hay alguna manera de hacer esto sin transmitir dos veces (la segunda vez para obtener el recuento)?


No es necesario transmitir dos veces. Simplemente llame a List.size() para el conteo:

public BigDecimal average(List<BigDecimal> bigDecimals, RoundingMode roundingMode) { BigDecimal sum = bigDecimals.stream() .map(Objects::requireNonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); return sum.divide(new BigDecimal(bigDecimals.size()), roundingMode); }


Si no le importa una dependencia de un tercero, lo siguiente funcionará con Eclipse Collections Collectors2.summarizingBigDecimal() llamando a getAverage con un MathContext , que incluye un RoundingMode .

MutableDoubleList doubles = DoubleLists.mutable.with(1.0, 2.0, 3.0, 4.0); List<BigDecimal> bigDecimals = doubles.collect(BigDecimal::new); BigDecimal average = bigDecimals.stream() .collect(Collectors2.summarizingBigDecimal(e -> e)) .getAverage(MathContext.DECIMAL32); Assert.assertEquals(BigDecimal.valueOf(2.5), average);

También se podría agregar una versión de getAverage para aceptar RoundingMode .

Nota: Soy un comendador de Eclipse Collections.


BigDecimal[] totalWithCount = bigDecimals.stream() .filter(bd -> bd != null) .map(bd -> new BigDecimal[]{bd, BigDecimal.ONE}) .reduce((a, b) -> new BigDecimal[]{a[0].add(b[0]), a[1].add(BigDecimal.ONE)}) .get(); BigDecimal mean = totalWithCount[0].divide(totalWithCount[1], roundingMode);

Descripción de texto opcional del código para aquellos que lo encuentran útil (ignore si encuentra el código suficientemente explicativo):

  • La lista de BigDecimals se convierte en una secuencia.
  • Los valores nulos se filtran fuera de la secuencia.
  • El flujo de BigDecimals se asigna como un flujo de dos matrices de elementos de BigDecimal donde el primer elemento es el elemento del flujo original y el segundo es el marcador de posición con valor uno.
  • En la reducción la a de (a,b) valor tiene la suma parcial en el primer elemento y el conteo parcial en el segundo elemento. El primer elemento del elemento b contiene cada uno de los valores de BigDecimal para agregar a la suma. El segundo elemento de b no se utiliza.
  • Reducir devuelve un opcional que estará vacío si la lista estaba vacía o contenía solo valores nulos.
    • Si la Opcional no está vacía, la función Optional.get () devolverá una matriz de dos elementos de BigDecimal donde la suma de BigDecimals está en el primer elemento y la cuenta de BigDecimals está en el segundo.
    • Si el opcional está vacío, se emitirá NoSuchElementException.
  • La media se calcula dividiendo la suma por el recuento.