如何使用 Streams 计算 BigDecimals 的平均值?
How to average BigDecimals using Streams?
我想采用以下方法:
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);
}
并使用 Streams api 更新它。这是我到目前为止得到的:
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);
}
有没有办法做到这一点而无需流式传输两次(第二次获取计数)?
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);
对于那些认为有帮助的代码的可选文本描述(如果您觉得代码足够自我解释,请忽略。):
- BigDecimals 列表已转换为流。
- 空值已从流中过滤掉。
- BigDecimals 的流被映射为 BigDecimal 的两个元素数组的流,其中第一个元素是原始流中的元素,第二个是值为 1 的占位符。
- 在 reduce 中,
a
of (a,b)
值在第一个元素中有部分和,在第二个元素中有部分计数。 b
元素的第一个元素包含要加到总和中的每个 BigDecimal 值。 b
的第二个元素未使用。
- 减少 returns 一个可选值,如果列表为空或仅包含空值,则该可选值将为空。
- 如果 Optional 不为空,Optional.get() 函数将 return BigDecimal 的两个元素数组,其中 BigDecimals 的总和在第一个元素中,BigDecimals 的计数是在第二个。
- 如果 Optional 为空,将抛出 NoSuchElementException。
- 平均值是用总和除以计数得到的。
或者,您可以使用此收集器实现:
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);
}
}
}
然后像这样使用它:
BigDecimal mean = bigDecimals.stream().collect(new BigDecimalAverageCollector());
注:示例使用Project Lombok注解来缩短胶水代码。
您不需要流式传输两次。只需调用 List.size()
即可计数:
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);
}
如果您不介意第三方依赖,以下内容将与 Eclipse Collections Collectors2.summarizingBigDecimal()
一起使用,方法是使用 MathContext
调用 getAverage
,其中包括 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);
可以添加一个 getAverage
版本来接受 RoundingMode
。
注意:我是 Eclipse Collections 的提交者。
我不想计算我的流的大小。然后,我使用累加器和组合器开发了以下内容。
Stream<BigDecimal> bigDecimalStream = ...
BigDecimalAverager sum = bigDecimalStream.reduce(new BigDecimalAverager(),
BigDecimalAverager::accept,
BigDecimalAverager::combine);
sum.average();
并且,这是身份代码 class;
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;
}
}
如何舍入除法值取决于您(我的情况使用 RoundingMode.HALF_UP)。
以上与
中解释的方式类似
我使用上述方法来获取 BigDecimal 对象列表的平均值。该列表允许空值。
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);
}
我想采用以下方法:
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);
}
并使用 Streams api 更新它。这是我到目前为止得到的:
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);
}
有没有办法做到这一点而无需流式传输两次(第二次获取计数)?
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);
对于那些认为有帮助的代码的可选文本描述(如果您觉得代码足够自我解释,请忽略。):
- BigDecimals 列表已转换为流。
- 空值已从流中过滤掉。
- BigDecimals 的流被映射为 BigDecimal 的两个元素数组的流,其中第一个元素是原始流中的元素,第二个是值为 1 的占位符。
- 在 reduce 中,
a
of(a,b)
值在第一个元素中有部分和,在第二个元素中有部分计数。b
元素的第一个元素包含要加到总和中的每个 BigDecimal 值。b
的第二个元素未使用。 - 减少 returns 一个可选值,如果列表为空或仅包含空值,则该可选值将为空。
- 如果 Optional 不为空,Optional.get() 函数将 return BigDecimal 的两个元素数组,其中 BigDecimals 的总和在第一个元素中,BigDecimals 的计数是在第二个。
- 如果 Optional 为空,将抛出 NoSuchElementException。
- 平均值是用总和除以计数得到的。
或者,您可以使用此收集器实现:
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);
}
}
}
然后像这样使用它:
BigDecimal mean = bigDecimals.stream().collect(new BigDecimalAverageCollector());
注:示例使用Project Lombok注解来缩短胶水代码。
您不需要流式传输两次。只需调用 List.size()
即可计数:
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);
}
如果您不介意第三方依赖,以下内容将与 Eclipse Collections Collectors2.summarizingBigDecimal()
一起使用,方法是使用 MathContext
调用 getAverage
,其中包括 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);
可以添加一个 getAverage
版本来接受 RoundingMode
。
注意:我是 Eclipse Collections 的提交者。
我不想计算我的流的大小。然后,我使用累加器和组合器开发了以下内容。
Stream<BigDecimal> bigDecimalStream = ...
BigDecimalAverager sum = bigDecimalStream.reduce(new BigDecimalAverager(),
BigDecimalAverager::accept,
BigDecimalAverager::combine);
sum.average();
并且,这是身份代码 class;
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;
}
}
如何舍入除法值取决于您(我的情况使用 RoundingMode.HALF_UP)。
以上与
中解释的方式类似我使用上述方法来获取 BigDecimal 对象列表的平均值。该列表允许空值。
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);
}