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 elementob
contiene cada uno de los valores de BigDecimal para agregar a la suma. El segundo elemento deb
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.