Как оценивать BigDecimals с помощью 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);
}

Есть ли способ сделать это без потоковой передачи дважды (второй раз, чтобы получить счет)?

Ответ 1

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, где первым элементом является элемент из исходного потока, а второй - держатель места со значением один.
  • В сокращении a of (a,b) значение имеет частичную сумму в первом элементе и частичный счет во втором элементе. Первый элемент элемента b содержит каждое из значений BigDecimal для добавления к сумме. Второй элемент b не используется.
  • Уменьшить возвращает необязательный параметр, который будет пустым, если список пуст или содержит только нулевые значения.
    • Если параметр Необязательный не пуст, функция Optional.get() вернет массив из двух элементов BigDecimal, где сумма BigDecimals находится в первом элементе, а счетчик BigDecimals находится во втором.
    • Если Необязательный пуст, будет выведено исключение NoSuchElementException.
  • Среднее значение вычисляется путем деления суммы на счет.

Ответ 2

Вам не нужно транслировать дважды. Просто позвоните 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);
}

Ответ 3

В качестве альтернативы вы можете использовать эту реализацию 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);
        }
    }

}

И используйте его так:

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

Примечание: в примере используются аннотации Project Lombok для сокращения кода клея.

Ответ 4

Если вы не возражаете зависимость третьей стороны, следующий будет работать с Eclipse, getAverage MathContext RoundingMode Коллекции Collectors2.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);

Можно также getAverage версию getAverage, чтобы принять RoundingMode.

Примечание: я являюсь коммиттером для Eclipse Collections.

Ответ 5

List<BigDecimal> bigDecimalList = Arrays.asList(
BigDecimal.TEN,
BigDecimal.valueOf(2.23),
BigDecimal.valueOf(12.32));


BigDecimal average = bigDecimalList.stream().reduce((x,y) -> x.add(y)).get()
.divide(BigDecimal.valueOf(bigDecimalList.size()),2));


System.out.println("avarage = "+average);

Ответ 6

Я не хотел считать размер моего потока. Затем я разработал следующее, используя аккумулятор и сумматор.

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

и вот код для класса идентичности;

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 для моего случая).

Вышеуказанное аналогично тому, как описано в fooobar.com/questions/1193349/...

Ответ 7

Другое решение,

/**
 * Compute the average of a list of BigDecimal objects
 *
 * @param bigDecimalList the list which contains the BigDecimals
 * @param roundingMode the RoundingMode to use.
 * @return a BigDecimal with the average of the list given as argument or null if the list is empty or full of null BigDecimal numbers.
 */
public BigDecimal bigDecimalAverage(List<BigDecimal> bigDecimalList, RoundingMode roundingMode) {
    // Get only the not null bigDecimals
    List<BigDecimal> bigDecimals = bigDecimalList.stream().filter(Objects::nonNull).collect(Collectors.toList());

    // Compute for the extreme cases
    if (bigDecimals.isEmpty())
        return null;
    if (bigDecimals.size() == 1)
        return bigDecimals.get(0);

    // Compute the average
    return bigDecimals.stream().reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(bigDecimals.size()), roundingMode);
}

Ответ 8

Я использую вышеупомянутый метод, чтобы получить среднее значение списка объектов 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);
}