java 如何使用 Streams 平均 BigDecimals?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31881561/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 19:17:53  来源:igfitidea点击:

How to average BigDecimals using Streams?

javacollectionsjava-8bigdecimaljava-stream

提问by Patrick Garner

I'm wanting to take the following method:

我想采用以下方法:

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);
}

and update it using the Streams api. Here's what I've got thus far:

并使用 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);
}

Is there a way to do this without streaming twice (the second time to get the count)?

有没有办法在不流式传输两次(第二次获得计数)的情况下做到这一点?

采纳答案by WillShackleford

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);

Optional text description of the code for those that are find that to be helpful (Ignore if you find the code sufficiently self explanatory.):

对于那些认为有帮助的代码的可选文本描述(如果您发现代码足够自我解释,请忽略。):

  • The list of BigDecimals is converted to a stream.
  • null values are filtered out of the stream.
  • The stream of BigDecimals is mapped to as stream of two element arrays of BigDecimal where the first element is the element from the original stream and the second is the place holder with value one.
  • In the reduce the aof (a,b)value has the partial sum in the first element and the partial count in the second element. The first element of the belement contains each of the BigDecimal values to add to the sum. The second element of bis not used.
  • Reduce returns an optional that will be empty if the list was empty or contained only null values.
    • If the Optional is not empty, Optional.get() function will return a two element array of BigDecimal where the sum of the BigDecimals is in the first element and the count of the BigDecimals is in the second.
    • If the Optional is empty, NoSuchElementException will be thrown.
  • The mean is computed by dividing the sum by the count.
  • BigDecimals 列表被转换为一个流。
  • 从流中过滤掉空值。
  • BigDecimals 流被映射为 BigDecimal 的两个元素数组的流,其中第一个元素是来自原始流的元素,第二个是值为 1 的占位符。
  • 在 reduce 中a(a,b)值在第一个元素中具有部分总和,在第二个元素中具有部分计数。元素的第一个b元素包含要添加到总和的每个 BigDecimal 值。b不使用的第二个元素。
  • 如果列表为空或仅包含空值,Reduce 将返回一个可选的值。
    • 如果 Optional 不为空,Optional.get() 函数将返回 BigDecimal 的双元素数组,其中 BigDecimals 的总和在第一个元素中,BigDecimals 的计数在第二个元素中。
    • 如果 Optional 为空,将抛出 NoSuchElementException。
  • 平均值是通过将总和除以计数来计算的。

回答by xehpuk

You don't need to stream twice. Simply call List.size()for the count:

您不需要流式传输两次。只需调用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);
}

回答by Novoj

Alternatively you can use this Collector implementation:

或者,您可以使用此收集器实现:

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);
        }
    }

}

And use it like that:

并像这样使用它:

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

Note: example uses Project Lombok annotations to shorten the glue code.

注意:示例使用 Project Lombok 注释来缩短粘合代码。

回答by Donald Raab

If you don't mind a third party dependency, the following will work with Eclipse CollectionsCollectors2.summarizingBigDecimal()by calling getAveragewith a MathContext, which includes a RoundingMode.

如果您不介意第三方依赖,以下将通过调用Eclipse CollectionsCollectors2.summarizingBigDecimal()getAverage使用MathContext,其中包括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);

A version of getAveragecould be added to accept RoundingModeas well.

getAverage也可以添加一个版本来接受RoundingMode

Note: I am a committer for Eclipse Collections.

注意:我是 Eclipse Collections 的提交者。

回答by Youness

I didn't want to count the size of my stream. Then, I developed the following using accumulator and combiner.

我不想计算我的流的大小。然后,我使用累加器和组合器开发了以下内容。

Stream<BigDecimal> bigDecimalStream = ...
BigDecimalAverager sum = bigDecimalStream.reduce(new BigDecimalAverager(),
                BigDecimalAverager::accept,
                BigDecimalAverager::combine);
sum.average();

and, here is the code for the identity 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;
    }

}

It is up to you how to round the divided value though (I use RoundingMode.HALF_UP for my case).

不过,如何舍入除法值取决于您(我使用 RoundingMode.HALF_UP 作为我的案例)。

The above is similar to the way explained in https://stackoverflow.com/a/23661052/1572286

以上类似于https://stackoverflow.com/a/23661052/1572286中解释的方式

回答by George Siggouroglou

I use the above method in order to get the average of a list of BigDecimal objects. The list allows null values.

我使用上述方法来获取 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);
}