Как оценивать 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);
}