Java百分比计算处理工具

1.说明

此工具可以帮助处理一个表格中各个百分比累计不等于100的问题
处理规则:调整横向或纵向累计项中最后一个不为0的项,使得总计百分比=100

设:一个 m ∗ n m*n mn的表格( m 行 n 列 m行n列 mn),则 X X X轴取值范围 [ 0 , m ] [0,m] [0,m] Y Y Y轴取值范围 [ 0 , n ] [0,n] [0,n]
坐标: i ∈ [ 1 , m ] , j ∈ [ 1 , n ] i\in[1,m], j\in[1,n] i[1,m],j[1,n]

在这里插入图片描述

具体可以处理以下3种类型:

2.横向处理(名称含有"XAxis"的方法)
  • 保证 1 1 1 n − 1 n-1 n1列累加值 = = = n n n列的值

∑ j = 1 n − 1 ( x i , y j ) = ( x i , y n ) \sum_{j=1}^{n-1}(x_i, y_j) = (x_i,y_n) j=1n1(xi,yj)=(xi,yn)

  • 或者保证 1 1 1 n n n列累加值 = 100 =100 =100

∑ j = 1 n ( x i , y j ) = 100 \sum_{j=1}^n(x_i, y_j)=100 j=1n(xi,yj)=100

3.纵向处理(名称含有"YAxis"的方法)
  • 保证 1 1 1 m − 1 m-1 m1行累加值 = = = m m m行的值

∑ i = 1 m − 1 ( x i , y j ) = ( x m , y j ) \sum_{i=1}^{m-1}(x_i, y_j) = (x_m,y_j) i=1m1(xi,yj)=(xm,yj)

  • 或者保证 1 1 1 m m m行累加值 = 100 =100 =100

∑ i = 1 m ( x i , y j ) = 100 \sum_{i=1}^m(x_i, y_j) = 100 i=1m(xi,yj)=100

4.交叉处理(名称含有"cross"的方法)

横向处理最后一行(第 m m m行),纵向处理最后一列(第 n n n列)

  • 保证最后一行的 1 1 1 n − 1 n-1 n1列累加值 = = = n n n列的值

∑ j = 1 n − 1 ( x m , y j ) = ( x m , y n ) \sum_{j=1}^{n-1}(x_m, y_j) = (x_m,y_n) j=1n1(xm,yj)=(xm,yn)

  • 且保证最后一列的 1 1 1 m − 1 m-1 m1行累加值 = = = m m m行的值

∑ i = 1 m − 1 ( x i , y n ) = ( x m , y n ) \sum_{i=1}^{m-1}(x_i, y_n) = (x_m,y_n) i=1m1(xi,yn)=(xm,yn)

5.源码
package com.visy.utils;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 百分比工具
 * @author visy.wang
 * @date 2024/8/13 13:03
 */
public class PercentageUtil {
    /**
     * 获取百分比(含四舍五入后两位小数部分)
     * @param a 分子
     * @param b 分母
     * @return 百分比
     */
    public static double getPercent(int a, int b){
        if(b == 0){
            return a==0 ? 0 : 100; // a/0 = 100%; 0/0 = 0%
        }else if(a == b){
            return 100; // a/a = 100%
        }else if(a == 0){
            return 0; // 0/b = 0%
        }
        //计算百分比,保留2位小数
        return BigDecimal.valueOf(a * 100L).divide(BigDecimal.valueOf(b), 2, RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * 百分比修正
     * @param matrix 原始矩阵
     * @param isReversed 是否反转矩阵(即将x和y轴互换),true-横向,false-纵向
     * @param includesLastLine 是否包含最后一行或列(反转后),不包含时以最后一行或列的值作为总计百分比,不包含则总计百分比按100处理
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元格的百分比,与converter二选一,优先级高于converter
     * @param converter 将百分比数字转换成需要的类型,与setter二选一
     * @param <T> 矩阵元素类型
     */
    private static <T> void correcting(
        T[][] matrix,
        boolean isReversed,
        boolean includesLastLine,
        Function<T,String> getter,
        BiConsumer<T,String> setter,
        Function<String,T> converter
    ){
        //原始矩阵的长和宽
        int xLength = matrix.length, yLength = matrix[0].length;
        //处理矩阵x轴和y轴的反转
        int iLength = isReversed ? yLength : xLength;
        int jLength = isReversed ? xLength : yLength;
        //保留或移除最后一行和列
        jLength = includesLastLine ? jLength : jLength-1;
        //遍历矩阵
        for(int i=0; i<iLength; i++){
            List<Integer> jList = new ArrayList<>(); //记录有占比单元格的行或列下标
            BigDecimal percentSum = BigDecimal.valueOf(0);//累计百分比

            for(int j=0; j<jLength; j++){
                T cell = isReversed ? matrix[j][i] : matrix[i][j];
                //获取当前项的百分比
                BigDecimal percent = new BigDecimal(getter.apply(cell));
                if(percent.doubleValue() > 0){
                    jList.add(j); //记录下标
                    //累加百分比
                    percentSum = percentSum.add(percent);
                }
            }

            //获取当前行或列的累计百分比
            if(percentSum.doubleValue() == 0){
                //累计百分比=0,无需特殊处理
                continue;
            }

            BigDecimal percentTotal; //总计百分比
            if(includesLastLine){
                //含最有一行或列的情况下,总计百分比取100
                percentTotal = BigDecimal.valueOf(100);
            }else{
                //不含最有一行或列的情况下,以最后一行或列的值作为总计百分比
                T sumCell = isReversed ? matrix[jLength][i] : matrix[i][jLength];
                percentTotal = new BigDecimal(getter.apply(sumCell)) ;
            }

            //计算累计百分比是否超过或小于总计百分比
            BigDecimal difference = percentTotal.subtract(percentSum);
            if(difference.doubleValue() == 0){
                //累计百分比刚好=总计百分比,无需特殊处理
                continue;
            }

			//定位可调整的单元格的行或列下标
            //可调整需要满足的条件:1.占比不为0;2.调整后不为0或负数
            final int I = i;
            Collections.reverse(jList);//反转
            int J = jList.stream().filter(j -> {
                T cell = isReversed ? matrix[j][I] : matrix[I][j];
                BigDecimal val = new BigDecimal(getter.apply(cell));
                return val.add(difference).doubleValue() > 0;
            }).findFirst().orElse(0);

            //调整定位到的单元格的值,是累计百分比=总计百分比
            T cell = isReversed ? matrix[J][i] : matrix[i][J];
            String newValue = new BigDecimal(getter.apply(cell)).add(difference).toString();
            if(setter != null){
                //更新单元格属性
                setter.accept(cell, newValue);
            }else if(converter != null){
                //更新整个单元格
                if(isReversed){
                    matrix[J][i] = converter.apply(newValue);
                }else{
                    matrix[i][J] = converter.apply(newValue);
                }
            }
        }
    }

    /**
     * 百分比修正
     * @param matrix 原始矩阵
     * @param isReversed 是否反转矩阵(即将x和y轴互换),true-横向,false-纵向
     * @param includesLastLine 是否包含最后一行或列(反转后)
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元格的百分比
     * @param <T> 矩阵元素类型
     */
    private static <T> void correcting(
        T[][] matrix,
        boolean isReversed,
        boolean includesLastLine,
        Function<T,String> getter,
        BiConsumer<T,String> setter
    ){
        correcting(matrix, isReversed, includesLastLine, getter, setter, null);
    }

    /**
     * 百分比修正
     * @param matrix 原始矩阵
     * @param isReversed 是否反转矩阵(即将x和y轴互换),true-横向,false-纵向
     * @param includesLastLine 是否包含最后一行或列(反转后)
     * @param converter 将百分比数字转换成需要的类型
     * @param <T> 矩阵元素类型
     */
    private static <T> void correcting(
        T[][] matrix,
        boolean isReversed,
        boolean includesLastLine,
        Function<String,T> converter
    ){
        correcting(matrix, isReversed, includesLastLine, String::valueOf, null, converter);
    }

    /**
     * 百分比修正(横向,不包含最有一列)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	4	1*	6
     * 1	1	4*	6
     * 1	2	3*	6
     * 4	11	9*	24
     * -------------------------------
     * @param matrix 原始矩阵
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元格的百分比
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingXAxis(
        T[][] matrix,
        Function<T,String> getter,
        BiConsumer<T,String> setter
    ){
        correcting(matrix, false, false, getter, setter);
    }

    /**
     * 百分比修正(横向,包含最后一列)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	94*
     * 1	4	3	92*
     * 1	1	3	95*
     * 1	2	7	90*
     * 4	11	10	75*
     * -------------------------------
     * @param matrix 原始矩阵
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元格的百分比
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingXAxisWithLastLine(
        T[][] matrix,
        Function<T,String> getter,
        BiConsumer<T,String> setter
    ){
        correcting(matrix, false, true, getter, setter);
    }

    /**
     * 百分比修正(纵向,不包含最后一行)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	4*	1*	6
     * 4	11	10	24
     * -------------------------------
     * @param matrix 原始矩阵
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元格的百分比
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingYAxis(
        T[][] matrix,
        Function<T,String> getter,
        BiConsumer<T,String> setter
    ){
        correcting(matrix, true, false, getter, setter);
    }

    /**
     * 百分比修正(纵向,包含最后一行)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 96*	91*	84*	76*
     * -------------------------------
     * @param matrix 原始矩阵
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元格的百分比
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingYAxisWithLastLine(
        T[][] matrix,
        Function<T,String> getter,
        BiConsumer<T,String> setter
    ){
        correcting(matrix, true, true, getter, setter);
    }

    /**
     * 百分比修正(横向,不包含最后一列)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	4	1*	6
     * 1	1	4*	6
     * 1	2	3*	6
     * 4	11	9*	24
     * -------------------------------
     * @param matrix 原始矩阵
     * @param converter 将百分比数字转换成需要的类型
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingXAxis(
        T[][] matrix,
        Function<String,T> converter
    ){
        correcting(matrix, false, false, converter);
    }

    /**
     * 百分比修正(横向,包含最后一列)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	94*
     * 1	4	3	92*
     * 1	1	3	95*
     * 1	2	7	90*
     * 4	11	10	75*
     * -------------------------------
     * @param matrix 原始矩阵
     * @param converter 将百分比数字转换成需要的类型
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingXAxisWithLastLine(
        T[][] matrix,
        Function<String,T> converter
    ){
        correcting(matrix, false, true, converter);
    }

    /**
     * 百分比修正(纵向,不包含最后一行)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	4*	1*	6
     * 4	11	10	24
     * -------------------------------
     * @param matrix 原始矩阵
     * @param converter 将百分比数字转换成需要的类型
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingYAxis(
        T[][] matrix,
        Function<String,T> converter
    ){
        correcting(matrix, true, false, converter);
    }

    /**
     * 百分比修正(纵向,包含最后一行)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 4	11	10	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	4	3	6
     * 1	1	3	6
     * 1	2	7	6
     * 96*	91*	84*	76*
     * -------------------------------
     * @param matrix 原始矩阵
     * @param converter 将百分比数字转换成需要的类型
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingYAxisWithLastLine(
        T[][] matrix,
        Function<String,T> converter
    ){
        correcting(matrix, true, true, converter);
    }

    /**
     * 百分比修正(交叉类型)
     * 只修正最后一行和最后一列
     * @param matrix 原始矩阵
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元个的百分比,与converter二选一,优先级高于converter
     * @param converter 将百分比数字转换成需要的类型,与setter二选一
     * @param <T> 矩阵元素类型
     */
    @SuppressWarnings("unchecked")
    private static <T> void correctingCross(
        T[][] matrix,
        Function<T,String> getter,
        BiConsumer<T,String> setter,
        Function<String,T> converter
    ){
        //原始矩阵的长和宽
        int xLength = matrix.length, yLength = matrix[0].length;
        Class<?> tClass = matrix[0][0].getClass();

        //找出最后一行之前总计百分比=100的一行
        int rowIndex = -1;
        for(int i=0; i<xLength-1; i++){
            if(new BigDecimal(getter.apply(matrix[i][yLength-1])).doubleValue() == 100){
                rowIndex = i; //只可能有一个,找到就跳出循环
                break;
            }
        }
        //找出最后一列之前总计百分比=100的一列
        int colIndex = -1;
        for(int j=0; j<yLength-1; j++){
            if(new BigDecimal(getter.apply(matrix[xLength-1][j])).doubleValue() == 100){
                colIndex = j; //只可能有一个,找到就跳出循环
                break;
            }
        }
        
        //提取最后一行和其他总计百分比=100的行
        T[][] rows = (T[][]) Array.newInstance(tClass, rowIndex>=0 ? 2 : 1, yLength);
        rows[0] = matrix[xLength-1];//复制最后一行
        if(rowIndex >= 0){ //复制其他总计百分比=100的行
            rows[1] = matrix[rowIndex];
        }

        //提取最后一列和其他总计百分比=100的列
        T[][] columns = (T[][]) Array.newInstance(tClass, colIndex>=0 ? 2 : 1, xLength);
        for(int x=0; x<xLength; x++){//复制最后一列
            columns[0][x] = matrix[x][yLength-1];
            if(colIndex >= 0){//复制其他总计百分比=100的列
                columns[1][x] = matrix[x][colIndex];
            }
        }

        //处理行的百分比
        correcting(rows, false, false, getter, setter, converter);
        //处理列的百分比
        correcting(columns, false, false, getter, setter, converter);
    }

    /**
     * 百分比修正(交叉类型)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	2	3	6
     * 2	2	3	7
     * 1	2	3	6
     * 5	8	12	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	2	3	6
     * 2	2	3	7
     * 1	2	3	5*
     * 5	8	11*	24
     * -------------------------------
     * @param matrix 原始矩阵
     * @param getter 获取单元格的百分比(内容为数字,不能含有非数字的字符)
     * @param setter 设置单元个的百分比
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingCross(
        T[][] matrix,
        Function<T,String> getter,
        BiConsumer<T,String> setter
    ){
        correctingCross(matrix, getter, setter, null);
    }

    /**
     * 百分比修正(交叉类型)
     * Before:
     * -------------------------------
     * 1	2	3	6
     * 1	2	3	6
     * 2	2	3	7
     * 1	2	3	6
     * 5	8	12	24
     * -------------------------------
     * After:
     * -------------------------------
     * 1	2	3	6
     * 1	2	3	6
     * 2	2	3	7
     * 1	2	3	5*
     * 5	8	11*	24
     * -------------------------------
     * @param matrix 原始矩阵
     * @param converter 将百分比数字转换成需要的类型
     * @param <T> 矩阵元素类型
     */
    public static <T> void correctingCross(
        T[][] matrix,
        Function<String,T> converter
    ){
        correctingCross(matrix, String::valueOf, null, converter);
    }

    /**
     * 双层List转矩阵
     * @param list 双层list
     * @param tClass 数组元素类型
     * @return 矩阵
     * @param <T> 元素类型
     */
    @SuppressWarnings("unchecked")
    public static <T> T[][] toMatrix(List<List<T>> list, Class<? super T> tClass){
        int xLength = list.size();
        int yLength = list.stream().mapToInt(List::size).max().orElse(0);

        T[][] matrix = (T[][]) Array.newInstance(tClass, xLength, yLength);
        for(int x=0; x<xLength; x++){
            List<T> lst = list.get(x);
            for(int y=0; y<lst.size(); y++){
                matrix[x][y] = lst.get(y);
            }
        }
        return matrix;
    }

    /**
     * 将列表转换为一行
     * @param list 列表
     * @param tClass 数组元素类型
     * @return 矩阵(只有一行)
     * @param <T> 元素类型
     */
    @SuppressWarnings("unchecked")
    public static <T> T[][] toOneRow(List<T> list, Class<? super T> tClass){
        int yLength = list.size();
        T[][] matrix = (T[][]) Array.newInstance(tClass, 1, yLength);
        for(int y=0; y<yLength; y++){
            matrix[0][y] = list.get(y);
        }
        return matrix;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值