Je veux prendre la méthode suivante:
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);
}
et le mettre à jour à l'aide de l'API Streams. Voici ce que j'ai jusqu'à présent:
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);
}
Existe-t-il un moyen de le faire sans diffuser deux fois (la deuxième fois pour obtenir le décompte)?
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);
Description textuelle facultative du code pour ceux qui trouvent cela utile (Ignorez si vous trouvez le code suffisamment explicite.):
a
de (a,b)
value a la somme partielle dans le premier élément et le compte partiel dans le deuxième élément. Le premier élément de l'élément b
contient chacune des valeurs BigDecimal à ajouter à la somme. Le deuxième élément de b
n'est pas utilisé.Vous n'avez pas besoin de diffuser deux fois. Appelez simplement List.size()
pour le nombre:
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);
}
Vous pouvez également utiliser cette implémentation 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);
}
}
}
Et utilisez-le comme ça:
BigDecimal mean = bigDecimals.stream().collect(new BigDecimalAverageCollector());
Remarque: l'exemple utilise des annotations Project Lombok pour raccourcir le code de collage.
Si cela ne vous dérange pas une dépendance tierce, ce qui suit fonctionnera avec Collections EclipseCollectors2.summarizingBigDecimal()
en appelant getAverage
avec un MathContext
, ce qui inclut 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);
Une version de getAverage
pourrait être ajoutée pour accepter également RoundingMode
.
Remarque: je suis un committer pour les collections Eclipse.
Je ne voulais pas compter la taille de mon flux. Ensuite, j'ai développé ce qui suit en utilisant un accumulateur et un combinateur.
Stream<BigDecimal> bigDecimalStream = ...
BigDecimalAverager sum = bigDecimalStream.reduce(new BigDecimalAverager(),
BigDecimalAverager::accept,
BigDecimalAverager::combine);
sum.average();
et, voici le code de la classe d'identité;
class BigDecimalAverager {
private final BigDecimal total;
private final int count;
public BigDecimalAverager() {
this.total = BigDecimal.ZERO;
this.count = 0;
}
public BigDecimalAverager(BigDecimal total, int count) {
this.total = total;
this.count = count;
}
public BigDecimalAverager accept(BigDecimal bigDecimal) {
return new BigDecimalAverager(total.add(bigDecimal), count + 1);
}
public BigDecimalAverager combine(BigDecimalAverager other) {
return new BigDecimalAverager(total.add(other.total), count + other.count);
}
public BigDecimal average() {
return count > 0 ? total.divide(new BigDecimal(count), RoundingMode.HALF_UP) : BigDecimal.ZERO;
}
}
C'est à vous de savoir comment arrondir la valeur divisée (j'utilise RoundingMode.HALF_UP pour mon cas).
Ce qui précède est similaire à la manière expliquée dans https://stackoverflow.com/a/23661052/1572286
J'utilise la méthode ci-dessus afin d'obtenir la moyenne d'une liste d'objets BigDecimal. La liste autorise les valeurs nulles.
public BigDecimal bigDecimalAverage(List<BigDecimal> bigDecimalList, RoundingMode roundingMode) {
// Filter the list removing null values
List<BigDecimal> bigDecimals = bigDecimalList.stream().filter(Objects::nonNull).collect(Collectors.toList());
// Special cases
if (bigDecimals.isEmpty())
return null;
if (bigDecimals.size() == 1)
return bigDecimals.get(0);
// Return the average of the BigDecimals in the list
return bigDecimals.stream().reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(bigDecimals.size()), roundingMode);
}