箱型图后端接口封装

一、写在前面

       业务中遇到箱型图,本来打算偷个懒,直接用同事封装好的工具类的,结果到了测试阶段,测试提出上/下 四分位计算方法不止一种,希望我们和之前的项目保持一致!在开发中最怕听到的就是保持一致了!结果,我所用的工具类和之前的项目计算方式还真的不一样。项目已经到了测试阶段,改工具类是最省事的办法了。

      改着改着,突然发现目前所用工具类有点麻烦,于是决定改造下。

     最大/小值、中位数,直接计算就行,没啥难度,主要是上四分位数和下四分位数,查了一圈(https://baike.baidu.com/item/%E5%9B%9B%E5%88%86%E4%BD%8D%E6%95%B0/5040599?fr=aladdin)概念,信心满满以为这下子能够完全看懂大佬封装的代码了(从而改造成工具类),结果卡在了“下标不为整数即数据个数不是4的整数”相关处理上了,最后还是去问大佬,才半懂了。记录下,以后要么慢慢就看懂了,要么直接使用 。

    和测试小姐姐验证上/下四分位计算是否正确,我只能对着代码计算,结果小姐姐说距离哪个数最近就乘3/4(0.75),哇!精髓了,对于不怎么理解算法的我来说简直了!

二、代码

package com.wisedu.hawkeye.domain.util;

import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 统计相关数据计算
 *
  * 最大值、最小值、平均值、中位数、上四分位、下四分位
 */
public class DataStatisticsCalculate {
    /**
     * 保留小数
     */
    private static final int DEFAULT_SCALE = 2;
    /**
     * 数据最小计算长度:计算 上/下 四分位数 时,数据的长度<= 3
     */
    private static final int QUARTILES_LIMIT_NUM = 3;
    /**
     * 上四分位
     */
    private static final BigDecimal NUMBER_75 = BigDecimal.valueOf(0.75);
    /**
     * 下四分位
     */
    private static final BigDecimal NUMBER_25 = BigDecimal.valueOf(0.25);

    private List<BigDecimal> dataList;

    public DataStatisticsCalculate(List<BigDecimal> dataList) {
        // 排序
        this.dataList = sort(dataList);
    }

    /**
     * @return 最大值
     */
    public BigDecimal getMax() {
        if (ObjectUtils.isEmpty(dataList)) {
            return BigDecimal.ZERO;
        }
        return dataList.get(dataList.size() - 1);
    }

    /**
     * @return 最小值
     */
    public BigDecimal getMin() {
        if (ObjectUtils.isEmpty(dataList)) {
            return BigDecimal.ZERO;
        }
        return dataList.get(0);
    }

    /**
     *
     * @return 平均数
     */
    public BigDecimal getAverage() {
        return twoNumDivide(sum(), BigDecimal.valueOf(dataList.size()));
    }

    /**
     *  数据从小到大排列后,第25%的数字
     * @return 下四分位
     */
    public BigDecimal getFourDown(){
        if(ObjectUtils.isEmpty(dataList)){
            return BigDecimal.ZERO;
        }
        int len=dataList.size();
        if(len<=QUARTILES_LIMIT_NUM){
            // 数据小于3条,下四分位数=最小值
            return dataList.get(0);
        }
        return calFourPositionNumber(NUMBER_25);
    }

    public BigDecimal getFourUp(){
        if(ObjectUtils.isEmpty(dataList)){
            return BigDecimal.ZERO;
        }
        int len=dataList.size();
        if(len<=QUARTILES_LIMIT_NUM){
            // 数据小于3条,上四分位数=最大值
            return dataList.get(len-1);
        }
        return calFourPositionNumber(NUMBER_75);

    }

    /**
     *  计算上/下四分位数对应的值
     * @param factor 上四分位/下分位
     * @return 对应的值
     * 计算公式
     * 下:Q1的位置= (n+1) × 0.25
     * 上:Q3的位置= (n+1) × 0.75
     * 例如:
     *
     * 数据总量: 7, 15, 36, 39, 40, 41 共6个数字
     * 下四分位分(0.25)   (6+1)/4=1.75   ,7与15之间,(1.75-1>0.5)更靠近于15,
     *   Q1 = 0.75*15+0.25*7 = 13
     *    ====》7*0.25+15*(1-0.25)
     * 上四分位分(0.75) (6+1)/4*3= 5.25,  40与41之间,(5.25-5<0.5)更接近40
     * Q3 = 0.25*41+0.75*40 = 40.25
     *    =====>
     *    40*0.75+41*(1-0.75)
     */
    private BigDecimal calFourPositionNumber(BigDecimal factor) {
        int len=dataList.size();
        BigDecimal value = BigDecimal.valueOf(len + 1).multiply(factor);
        int startIndex = value.setScale(0, BigDecimal.ROUND_DOWN).intValue() - 1;
        float pointNumber = value.floatValue() - (startIndex + 1);
        if (pointNumber == 0.0f) {
            return dataList.get(startIndex).setScale(2, BigDecimal.ROUND_HALF_UP);
        }

        if (pointNumber <= 0.5f) {
            return dataList.get(startIndex).multiply(BigDecimal.valueOf(1 - pointNumber))
                    .add(dataList.get(startIndex + 1).multiply(BigDecimal.valueOf(pointNumber)))
                    .setScale(2, BigDecimal.ROUND_HALF_UP);
        } else {
            return dataList.get(startIndex + 1).multiply(BigDecimal.valueOf(pointNumber))
                    .add(dataList.get(startIndex).multiply(BigDecimal.valueOf(1 - pointNumber)))
                    .setScale(2, BigDecimal.ROUND_HALF_UP);
        }
    }

    /**
     * 总数
     * @return
     */
    private BigDecimal sum() {
        BigDecimal result = BigDecimal.ZERO;
        for (BigDecimal num : dataList) {
            result = result.add(num);
        }
        return result;
    }

    /**
     * @return 中位数
     */
    public BigDecimal getMiddle() {
        if (ObjectUtils.isEmpty(dataList)) {
            return BigDecimal.ZERO;
        }
        int len = dataList.size();
        if (len == 1) {
            return dataList.get(0);
        }
        if (len % 2 == 1) {
            // 奇数
            return dataList.get(len / 2);
        } else {
            // 偶数
            int endIndex = len / 2;
            int startIndex = endIndex - 1;
            return calAverage(dataList.get(startIndex), dataList.get(endIndex));
        }
    }

    /**
     * @param num1 第一个数
     * @param num2 第二个数
     * @return 两个数的平均数
     */
    private BigDecimal calAverage(BigDecimal num1, BigDecimal num2) {
        return twoNumDivide(num1.add(num2), BigDecimal.valueOf(2));
    }

    /**
     * @param num1 被除数
     * @param num2 除数
     * @return nm1/num2 的结果,四舍五入,并保留小树
     */
    private BigDecimal twoNumDivide(BigDecimal num1, BigDecimal num2) {
        return num1.divide(num2, DEFAULT_SCALE, RoundingMode.HALF_UP);
    }

    /**
     * @param dataList 数据源
     * @return 升序:排序后的数据
     */
    private List<BigDecimal> sort(List<BigDecimal> dataList) {
        return dataList.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值