|
|
|
|
@ -0,0 +1,312 @@
|
|
|
|
|
package cn.iocoder.yudao.module.iot.util;
|
|
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
import java.util.DoubleSummaryStatistics;
|
|
|
|
|
import java.text.DecimalFormat;
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.math.RoundingMode;
|
|
|
|
|
|
|
|
|
|
public class MapListStatsCalculator {
|
|
|
|
|
|
|
|
|
|
// 默认保留小数位数
|
|
|
|
|
private static final int DEFAULT_SCALE = 2;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定key列表的最大值(四舍五入)
|
|
|
|
|
*/
|
|
|
|
|
public static Map<String, Double> getMaxValues(List<Map<String, Object>> dataList, List<String> keys) {
|
|
|
|
|
return getMaxValues(dataList, keys, DEFAULT_SCALE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Map<String, Double> getMaxValues(List<Map<String, Object>> dataList, List<String> keys, int scale) {
|
|
|
|
|
return keys.stream()
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
key -> key,
|
|
|
|
|
key -> {
|
|
|
|
|
OptionalDouble max = dataList.stream()
|
|
|
|
|
.filter(map -> map.containsKey(key) && map.get(key) != null)
|
|
|
|
|
.mapToDouble(map -> convertToDouble(map.get(key)))
|
|
|
|
|
.max();
|
|
|
|
|
return roundValue(max.orElse(Double.NaN), scale);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定key列表的最小值(四舍五入)
|
|
|
|
|
*/
|
|
|
|
|
public static Map<String, Double> getMinValues(List<Map<String, Object>> dataList, List<String> keys) {
|
|
|
|
|
return getMinValues(dataList, keys, DEFAULT_SCALE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Map<String, Double> getMinValues(List<Map<String, Object>> dataList, List<String> keys, int scale) {
|
|
|
|
|
return keys.stream()
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
key -> key,
|
|
|
|
|
key -> {
|
|
|
|
|
OptionalDouble min = dataList.stream()
|
|
|
|
|
.filter(map -> map.containsKey(key) && map.get(key) != null)
|
|
|
|
|
.mapToDouble(map -> convertToDouble(map.get(key)))
|
|
|
|
|
.min();
|
|
|
|
|
return roundValue(min.orElse(Double.NaN), scale);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定key列表的平均值(四舍五入)
|
|
|
|
|
*/
|
|
|
|
|
public static Map<String, Double> getAvgValues(List<Map<String, Object>> dataList, List<String> keys) {
|
|
|
|
|
return getAvgValues(dataList, keys, DEFAULT_SCALE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Map<String, Double> getAvgValues(List<Map<String, Object>> dataList, List<String> keys, int scale) {
|
|
|
|
|
return keys.stream()
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
key -> key,
|
|
|
|
|
key -> {
|
|
|
|
|
OptionalDouble avg = dataList.stream()
|
|
|
|
|
.filter(map -> map.containsKey(key) && map.get(key) != null)
|
|
|
|
|
.mapToDouble(map -> convertToDouble(map.get(key)))
|
|
|
|
|
.average();
|
|
|
|
|
return roundValue(avg.orElse(Double.NaN), scale);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定key列表的最大值与最小值相差(四舍五入)
|
|
|
|
|
*/
|
|
|
|
|
public static Map<String, Double> getRangeValues(List<Map<String, Object>> dataList, List<String> keys) {
|
|
|
|
|
return getRangeValues(dataList, keys, DEFAULT_SCALE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Map<String, Double> getRangeValues(List<Map<String, Object>> dataList, List<String> keys, int scale) {
|
|
|
|
|
return keys.stream()
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
key -> key,
|
|
|
|
|
key -> {
|
|
|
|
|
DoubleSummaryStatistics stats = dataList.stream()
|
|
|
|
|
.filter(map -> map.containsKey(key) && map.get(key) != null)
|
|
|
|
|
.mapToDouble(map -> convertToDouble(map.get(key)))
|
|
|
|
|
.summaryStatistics();
|
|
|
|
|
double range = stats.getCount() > 0 ? stats.getMax() - stats.getMin() : 0.0;
|
|
|
|
|
return roundValue(range, scale);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 一次性获取所有统计信息(四舍五入)
|
|
|
|
|
*/
|
|
|
|
|
public static Map<String, Map<String, Double>> getAllStats(List<Map<String, Object>> dataList, List<String> keys) {
|
|
|
|
|
return getAllStats(dataList, keys, DEFAULT_SCALE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Map<String, Map<String, Double>> getAllStats(List<Map<String, Object>> dataList, List<String> keys, int scale) {
|
|
|
|
|
return keys.stream()
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
key -> key,
|
|
|
|
|
key -> {
|
|
|
|
|
DoubleSummaryStatistics stats = dataList.stream()
|
|
|
|
|
.filter(map -> map.containsKey(key) && map.get(key) != null)
|
|
|
|
|
.mapToDouble(map -> convertToDouble(map.get(key)))
|
|
|
|
|
.summaryStatistics();
|
|
|
|
|
|
|
|
|
|
Map<String, Double> result = new HashMap<>();
|
|
|
|
|
result.put("max", roundValue(stats.getCount() > 0 ? stats.getMax() : 0.0, scale));
|
|
|
|
|
result.put("min", roundValue(stats.getCount() > 0 ? stats.getMin() : 0.0, scale));
|
|
|
|
|
result.put("avg", roundValue(stats.getCount() > 0 ? stats.getAverage() : 0.0, scale));
|
|
|
|
|
result.put("range", roundValue(stats.getCount() > 0 ? stats.getMax() - stats.getMin() : 0.0, scale));
|
|
|
|
|
result.put("count", (double) stats.getCount()); // 数量保持整数
|
|
|
|
|
result.put("sum", roundValue(stats.getSum(), scale));
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 对象转换为Double
|
|
|
|
|
*/
|
|
|
|
|
private static double convertToDouble(Object value) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
if (value instanceof Number) {
|
|
|
|
|
return ((Number) value).doubleValue();
|
|
|
|
|
}
|
|
|
|
|
if (value instanceof String) {
|
|
|
|
|
try {
|
|
|
|
|
return Double.parseDouble(value.toString().trim());
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (value instanceof Boolean) {
|
|
|
|
|
return (Boolean) value ? 1.0 : 0.0;
|
|
|
|
|
}
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 四舍五入方法 - 使用BigDecimal(最准确)
|
|
|
|
|
*/
|
|
|
|
|
public static double roundValue(double value, int scale) {
|
|
|
|
|
if (Double.isNaN(value) || Double.isInfinite(value)) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
return BigDecimal.valueOf(value)
|
|
|
|
|
.setScale(scale, RoundingMode.HALF_UP)
|
|
|
|
|
.doubleValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 四舍五入方法 - 使用DecimalFormat(格式化输出)
|
|
|
|
|
*/
|
|
|
|
|
public static String roundValueToString(double value, int scale) {
|
|
|
|
|
if (Double.isNaN(value)) {
|
|
|
|
|
return "NaN";
|
|
|
|
|
}
|
|
|
|
|
if (Double.isInfinite(value)) {
|
|
|
|
|
return value > 0 ? "Infinity" : "-Infinity";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 构建格式字符串
|
|
|
|
|
StringBuilder pattern = new StringBuilder("0");
|
|
|
|
|
if (scale > 0) {
|
|
|
|
|
pattern.append(".");
|
|
|
|
|
for (int i = 0; i < scale; i++) {
|
|
|
|
|
pattern.append("0");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DecimalFormat df = new DecimalFormat(pattern.toString());
|
|
|
|
|
df.setRoundingMode(RoundingMode.HALF_UP);
|
|
|
|
|
return df.format(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 格式化数字为字符串,自动去除末尾的0
|
|
|
|
|
*/
|
|
|
|
|
public static String formatValue(double value, int maxScale) {
|
|
|
|
|
if (Double.isNaN(value) || Double.isInfinite(value)) {
|
|
|
|
|
return String.valueOf(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 先四舍五入
|
|
|
|
|
double rounded = roundValue(value, maxScale);
|
|
|
|
|
|
|
|
|
|
// 转换为字符串,去除末尾的0
|
|
|
|
|
String str = String.valueOf(rounded);
|
|
|
|
|
if (str.contains(".")) {
|
|
|
|
|
str = str.replaceAll("0*$", "").replaceAll("\\.$", "");
|
|
|
|
|
}
|
|
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取统计信息(字符串格式,已四舍五入)
|
|
|
|
|
*/
|
|
|
|
|
public static Map<String, Map<String, String>> getAllStatsAsString(
|
|
|
|
|
List<Map<String, Object>> dataList,
|
|
|
|
|
List<String> keys,
|
|
|
|
|
int scale) {
|
|
|
|
|
|
|
|
|
|
return keys.stream()
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
key -> key,
|
|
|
|
|
key -> {
|
|
|
|
|
DoubleSummaryStatistics stats = dataList.stream()
|
|
|
|
|
.filter(map -> map.containsKey(key) && map.get(key) != null)
|
|
|
|
|
.mapToDouble(map -> convertToDouble(map.get(key)))
|
|
|
|
|
.summaryStatistics();
|
|
|
|
|
|
|
|
|
|
Map<String, String> result = new HashMap<>();
|
|
|
|
|
result.put("max", roundValueToString(stats.getCount() > 0 ? stats.getMax() : 0.0, scale));
|
|
|
|
|
result.put("min", roundValueToString(stats.getCount() > 0 ? stats.getMin() : 0.0, scale));
|
|
|
|
|
result.put("avg", roundValueToString(stats.getCount() > 0 ? stats.getAverage() : 0.0, scale));
|
|
|
|
|
result.put("range", roundValueToString(stats.getCount() > 0 ? stats.getMax() - stats.getMin() : 0.0, scale));
|
|
|
|
|
result.put("count", String.valueOf(stats.getCount()));
|
|
|
|
|
result.put("sum", roundValueToString(stats.getSum(), scale));
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用示例
|
|
|
|
|
*/
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
// 创建测试数据
|
|
|
|
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
Map<String, Object> map1 = new HashMap<>();
|
|
|
|
|
map1.put("temperature", 25.56789);
|
|
|
|
|
map1.put("humidity", 60.12345);
|
|
|
|
|
map1.put("pressure", 100.23456);
|
|
|
|
|
dataList.add(map1);
|
|
|
|
|
|
|
|
|
|
Map<String, Object> map2 = new HashMap<>();
|
|
|
|
|
map2.put("temperature", 28.33333);
|
|
|
|
|
map2.put("humidity", 55.55555);
|
|
|
|
|
map2.put("pressure", 102.77777);
|
|
|
|
|
dataList.add(map2);
|
|
|
|
|
|
|
|
|
|
Map<String, Object> map3 = new HashMap<>();
|
|
|
|
|
map3.put("temperature", 22.11111);
|
|
|
|
|
map3.put("humidity", 65.99999);
|
|
|
|
|
map3.put("pressure", 98.65432);
|
|
|
|
|
dataList.add(map3);
|
|
|
|
|
|
|
|
|
|
// 要统计的key
|
|
|
|
|
List<String> keys = Arrays.asList("temperature", "humidity", "pressure");
|
|
|
|
|
|
|
|
|
|
System.out.println("=== 默认保留2位小数 ===");
|
|
|
|
|
Map<String, Map<String, Double>> allStats = getAllStats(dataList, keys);
|
|
|
|
|
printStats(allStats);
|
|
|
|
|
|
|
|
|
|
System.out.println("\n=== 保留3位小数 ===");
|
|
|
|
|
Map<String, Map<String, Double>> stats3 = getAllStats(dataList, keys, 3);
|
|
|
|
|
printStats(stats3);
|
|
|
|
|
|
|
|
|
|
System.out.println("\n=== 字符串格式(保留2位小数)===");
|
|
|
|
|
Map<String, Map<String, String>> stringStats = getAllStatsAsString(dataList, keys, 2);
|
|
|
|
|
printStringStats(stringStats);
|
|
|
|
|
|
|
|
|
|
System.out.println("\n=== 单独使用四舍五入方法 ===");
|
|
|
|
|
double testValue = 123.456789;
|
|
|
|
|
System.out.println("原始值: " + testValue);
|
|
|
|
|
System.out.println("保留0位: " + roundValue(testValue, 0));
|
|
|
|
|
System.out.println("保留2位: " + roundValue(testValue, 2));
|
|
|
|
|
System.out.println("保留4位: " + roundValue(testValue, 4));
|
|
|
|
|
System.out.println("格式化输出: " + roundValueToString(testValue, 2));
|
|
|
|
|
System.out.println("智能格式化: " + formatValue(testValue, 2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void printStats(Map<String, Map<String, Double>> stats) {
|
|
|
|
|
for (Map.Entry<String, Map<String, Double>> entry : stats.entrySet()) {
|
|
|
|
|
System.out.println("\n列: " + entry.getKey());
|
|
|
|
|
Map<String, Double> colStats = entry.getValue();
|
|
|
|
|
System.out.printf(" 最大值: %.4f%n", colStats.get("max"));
|
|
|
|
|
System.out.printf(" 最小值: %.4f%n", colStats.get("min"));
|
|
|
|
|
System.out.printf(" 平均值: %.4f%n", colStats.get("avg"));
|
|
|
|
|
System.out.printf(" 差值: %.4f%n", colStats.get("range"));
|
|
|
|
|
System.out.printf(" 数量: %.0f%n", colStats.get("count"));
|
|
|
|
|
System.out.printf(" 总和: %.4f%n", colStats.get("sum"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void printStringStats(Map<String, Map<String, String>> stats) {
|
|
|
|
|
for (Map.Entry<String, Map<String, String>> entry : stats.entrySet()) {
|
|
|
|
|
System.out.println("\n列: " + entry.getKey());
|
|
|
|
|
Map<String, String> colStats = entry.getValue();
|
|
|
|
|
System.out.println(" 最大值: " + colStats.get("max"));
|
|
|
|
|
System.out.println(" 最小值: " + colStats.get("min"));
|
|
|
|
|
System.out.println(" 平均值: " + colStats.get("avg"));
|
|
|
|
|
System.out.println(" 差值: " + colStats.get("range"));
|
|
|
|
|
System.out.println(" 数量: " + colStats.get("count"));
|
|
|
|
|
System.out.println(" 总和: " + colStats.get("sum"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|