1.说明
此工具可以帮助处理一个表格中各个百分比累计不等于100的问题
设一个m*n的表格(m行n列),具体可以处理以下3种类型:
2.横向处理(名称含有"XAxis"的方法)
- 保证1到n-1列累加值=第n列的值
∑ j = 1 n − 1 ( x i , y j ) = x i y n \sum_{j=1}^{n-1}(x_i, y_j) = x_iy_n ∑j=1n−1(xi,yj)=xiyn
- 或者保证1到n列累加值=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到m-1行累加值=第m行的值
∑ i = 1 m − 1 ( x i , y j ) = x m y j \sum_{i=1}^{m-1}(x_i, y_j) = x_my_j ∑i=1m−1(xi,yj)=xmyj
- 或者保证1到m行累加值=100
∑ i = 1 m ( x i , y j ) = 100 \sum_{i=1}^m(x_i, y_j) = 100 ∑i=1m(xi,yj)=100
3.交叉处理(名称含有"cross"的方法)
- 横向处理最后一行(第m行),纵向处理最后一列(第n列),保证最后一行1到n-1列累加值=(m,n)的值,最后一列1到m-1行累加值=(m,n)的值。
∑ j = 1 n − 1 ( x m , y j ) = x m y n \sum_{j=1}^{n-1}(x_m, y_j) = x_my_n ∑j=1n−1(xm,yj)=xmyn
∑ i = 1 m − 1 ( x i , y n ) = x m y n \sum_{i=1}^{m-1}(x_i, y_n) = x_my_n ∑i=1m−1(xi,yn)=xmyn
4.源码
package com.visy.utils;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.RoundingMode;
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++){
int lastOfJ = 0; //记录最后一个有占比单元格的行或列下标
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){
lastOfJ = j; //记录下标
//累加百分比
percentSum = percentSum.add(percent);
}
}
//获取当前行或列的累计百分比
if(percentSum.doubleValue() == 0){
//累计百分比=0,无需特殊处理
continue;
}
BigDecimal total; //总计百分比
if(includesLastLine){
//含最有一行或列的情况下,总计百分比取100
total = BigDecimal.valueOf(100);
}else{
//不含最有一行或列的情况下,以最后一行或列的值作为总计百分比
T sumCell = isReversed ? matrix[jLength][i] : matrix[i][jLength];
total = new BigDecimal(getter.apply(sumCell)) ;
}
//计算累计百分比是否超过或小于总计百分比
BigDecimal difference = total.subtract(percentSum);
if(difference.doubleValue() == 0){
//累计百分比刚好=总计百分比,无需特殊处理
continue;
}
//累计百分比!=100,调整最后一个有占比的单元格,使得总百分比=100
T cell = isReversed ? matrix[lastOfJ][i] : matrix[i][lastOfJ];
String newValue = new BigDecimal(getter.apply(cell)).add(difference).toString();
if(setter != null){
//更新单元格属性
setter.accept(cell, newValue);
}else if(converter != null){
//更新整个单元格
if(isReversed){
matrix[lastOfJ][i] = converter.apply(newValue);
}else{
matrix[i][lastOfJ] = 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> 矩阵元素类型
*/
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;
int lastIndex = 0; //累计百分比时最后一个有占比的单元格下标位置
BigDecimal percentSum = BigDecimal.valueOf(0); //累计百分比
//遍历最后一行
for(int y=0; y<yLength-1; y++){
BigDecimal value = new BigDecimal(getter.apply(matrix[xLength-1][y]));
if(value.doubleValue() > 0){
lastIndex = y;
percentSum = percentSum.add(value);
}
}
//获取总计百分比
T sumCell = matrix[xLength-1][yLength-1];
BigDecimal total = new BigDecimal(getter.apply(sumCell));
//计算最后一行累计百分比和总计百分比的差值
BigDecimal difference = total.subtract(percentSum);
if(difference.doubleValue() != 0){
//有差值,则调整最后一个有占比的单元格
T cell = matrix[xLength-1][lastIndex];
String newValue = new BigDecimal(getter.apply(cell)).add(difference).toString();
if(setter != null){
setter.accept(cell, newValue);
}else if(converter != null){
matrix[xLength-1][lastIndex] = converter.apply(newValue);
}
}
//遍历最后一列
lastIndex = 0;
percentSum = BigDecimal.valueOf(0);
for(int x=0; x<xLength-1; x++){
BigDecimal value = new BigDecimal(getter.apply(matrix[x][yLength-1]));
if(value.doubleValue() > 0){
lastIndex = x;
percentSum = percentSum.add(value);
}
}
//计算最后一列累计百分比和总计百分比的差值
difference = total.subtract(percentSum);
if(difference.doubleValue() != 0){
//有差值,则调整最后一个有占比的单元格
T cell = matrix[lastIndex][yLength-1];
String newValue = new BigDecimal(getter.apply(cell)).add(difference).toString();
if(setter != null){
setter.accept(cell, newValue);
}else if(converter != null){
matrix[lastIndex][yLength-1] = converter.apply(newValue);
}
}
}
/**
* 百分比修正(交叉类型)
* 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;
}
}