1.说明
此工具可以帮助处理一个表格中各个百分比累计不等于100的问题
处理规则:调整横向或纵向累计项中最后一个不为0的项,使得总计百分比=100
设:一个 m ∗ n m*n m∗n的表格( m 行 n 列 m行n列 m行n列),则 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 n−1列累加值 = = =第 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=1n−1(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 m−1行累加值 = = =第 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=1m−1(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 n−1列累加值 = = =第 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=1n−1(xm,yj)=(xm,yn)
- 且保证最后一列的 1 1 1到 m − 1 m-1 m−1行累加值 = = =第 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=1m−1(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;
}
}