使用Streams API收集器平均BigDecimals

时间:2019-01-20 17:06:16

标签: java java-8 java-stream average bigdecimal

当前基于双重奖品的方法。

partial_fit()

购物基本上是一张地图:public Map<String, BigDecimal> averageProductPriceInCategory() { return shopping.entrySet() .stream() .flatMap(e -> e.getValue().keySet().stream()) .collect(Collectors.groupingBy(Product::getCategory, Collectors.averagingDouble(Product::getPrize))); }

  • 外键代表客户
  • 内部键代表产品。产品类成员是名称,类别,价格(以前是double类型)-希望使用price作为BigDecimal类型将提供的代码重构为一个代码
  • 内部地图值(整数)代表属于特定客户的指定产品的数量

以下代码段仅可用于计算属于指定类别的产品的总奖金。不确定如何使用BigDecimals计算有关类别的平均产品奖励

Map<Client, Map<Product,Integer>>

4 个答案:

答案 0 :(得分:2)

看看如何实现Collectors.averagingDoubleCollectors.averagingInt

// Register Action
exports.register = (req, res) => {
  function getAvailableFunds() {
    const availableFunds = 0;
    return availableFunds;
  }

  async function getAccountBill() {
    const accountBill = `2222${Math.floor(
      Math.random() * 90000000000000000000,
    ) + 10000000000000000000}`;

    try {
      const isAccountBill = await Bill.findOne({
        where: {
          account_bill: accountBill,
        },
      });
      return isAccountBill ? await getAccountBill() : accountBill;
    } catch(e) {
      console.error(e);
    }
  }

  function getAccountBalanceHistory() {
    const accountBalanceHistory = '0,0';
    return accountBalanceHistory;
  }

  function getTodayDate() {
    const today = new Date();
    return today;
  }

  User.findOne({
    where: { login: req.body.login },
  }).then(isUser => {
    if (!isUser) {
      bcrypt.hash(req.body.password, 10, (err, hash) => {
        req.body.password = hash;

        const user = await User.create({
          login: req.body.login,
          password: req.body.password,
          name: req.body.name,
          surname: req.body.surname,
          email: req.body.email,
          date_registration: getTodayDate(),
        });
        const account_bill = await getAccountBill();
        const bill = await Bill.create({
          id_owner: user.id,
          account_bill,
          available_funds: getAvailableFunds(),
        })
        const additional = await Additional.create({
          id_owner: user.id,
          account_balance_history: getAccountBalanceHistory(),
        });
        res.status(200).json({ register: true });

          }),
        );
      });
    } else {
      res.status(400).json({ error: 'User already exists.' });
    }
  });
};

从本质上讲,您需要一种可变的累积类型,该类型将包含public static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper) { return new CollectorImpl<>( () -> new long[2], (a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; }, (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; }, a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID); } (这是产品价格的总和)和BigDecimal(这是已处理的许多产品)。有了这些,问题就归结为编写简单的Collector<Product, AccumulationType, BigDecimal>

我简化了一个示例,并删除了getters / setter和一个全参数的构造函数。除了嵌套类int,您还可以对2个元素使用任何可变的holder类。

ProductPriceSummary

答案 1 :(得分:0)

出于理解目的,我将操作分为两个步骤。如果您愿意,可以将两个步骤结合在一起。

    Map<String, BigDecimal[]> stringMap = shopping.entrySet()
            .stream()
            .flatMap(e -> e.getValue().keySet().stream())
            .collect(Collectors.groupingBy(Product::getCategory,Collectors.collectingAndThen(Collectors.toList(),l -> l.stream().map(Product::getPrize)
                    .map(bd -> new BigDecimal[]{bd, BigDecimal.ONE})
                    .reduce((a, b) -> new BigDecimal[]{a[0].add(b[0]), a[1].add(BigDecimal.ONE)})
                    .get()
            )));

    Map<String, BigDecimal> stringBigDecimalMap = stringMap.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey,e -> e.getValue()[0].divide(e.getValue()[1])));

说明:

  • 在第一个操作中,将分组后的BigDecimals流映射为BigDecimal的两个元素数组的流,其中第一个元素是原始流中的元素,第二个元素是值为1的占位符。
  • 在约简中,a的{​​{1}}值在第一个元素中具有部分和,在第二个元素中具有部分计数。 (a,b)元素的第一个元素包含要添加到总和的每个BigDecimal值。 b的第二个元素未使用。
  • Reduce返回一个可选参数,如果列表为空或仅包含空值,则为空。
    • 如果Optional不为空,则Optional.get()函数将返回BigDecimal的两个元素数组,其中BigDecimals的总和在第一个元素中,而BigDecimals的计数在第二个元素中。
    • 如果Optional为空,则将引发NoSuchElementException。
  • 通过将总和除以计数来计算平均值。这是针对中间映射b
  • 中的每个条目完成的

答案 2 :(得分:0)

这基于[Double|Int]Pipeline.average()的源代码。它使用数组存储项目数(在索引0处)和总和(在索引1处)。

public Map<String, BigDecimal> averageProductPriceInCategory() {
  return shopping.entrySet().stream()
      .flatMap(entry -> entry.getValue().keySet().stream())
      .collect(Collectors.groupingBy(
          Product::getCategory,
          Collector.of(
              () -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO},
              (array, product) -> {
                array[0] = array[0].add(BigDecimal.ONE);
                array[1] = array[1].add(product.getPrice());
              },
              (left, right) -> {
                left[0] = left[0].add(right[0]);
                left[1] = left[1].add(right[1]);
                return left;
              },
              array -> array[0].compareTo(BigDecimal.ONE) <= 0 
                       ? array[1] 
                       : array[1].divide(array[0], RoundingMode.HALF_UP)
          )
      ));
}

这有一些缺点:

  1. 不方便在多个地方使用。
  2. 不一定容易遵循。
  3. 将计数存储为BigDecimal,使用intlong会更有意义。

可以通过将收集器提取到自定义类中来解决这些问题(就像Andrew's answer一样。)

答案 3 :(得分:0)

您可以像这样创建自己的收集器:

Collector<BigDecimal, BigDecimal[], BigDecimal> avgCollector = Collector.of(
      () -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO},
      (pair, val) -> {
        pair[0] = pair[0].add(val);
        pair[1] = pair[1].add(BigDecimal.ONE);
      },
      (pair1, pair2) -> new BigDecimal[]{pair1[0].add(pair2[0]), pair1[1].add(pair2[1])},
      (pair) -> pair[0].divide(pair[1], 2, RoundingMode.HALF_UP)
);

...然后使用它:

Map<String, BigDecimal> totalProductPriceInEachCategory = shopping.values().stream()
      .flatMap(e -> e.keySet().stream())
      .collect(groupingBy(Product::getCategory, mapping(Product::getPrice, avgCollector)));