From c6bb187ae820ead3f3f38342ffc1c0f1c9fe4bdf Mon Sep 17 00:00:00 2001 From: lihanqi <13868246742@163.com> Date: Thu, 7 Aug 2025 19:53:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=A9=E7=A5=A8=E5=8A=A9=E6=89=8B1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xyaicpzs/service/BallAnalysisService.java | 1094 +++++++++++------ .../xy/xyaicpzs/task/PredictResultTask.java | 2 +- src/main/resources/application.yml | 18 +- 3 files changed, 732 insertions(+), 382 deletions(-) diff --git a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java index ac45a13..414ce9c 100644 --- a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java +++ b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java @@ -24,6 +24,30 @@ import com.xy.xyaicpzs.domain.vo.RedBallHitRateVO; @Service public class BallAnalysisService { + /** + * 球号和系数的对应关系 + */ + public static class BallWithCoefficient { + private Integer ballNumber; + private Double coefficient; + private Integer masterBallNumber; // 主球号,用于标识来源 + + public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) { + this.ballNumber = ballNumber; + this.coefficient = coefficient; + this.masterBallNumber = masterBallNumber; + } + + public Integer getBallNumber() { return ballNumber; } + public Double getCoefficient() { return coefficient; } + public Integer getMasterBallNumber() { return masterBallNumber; } + + @Override + public String toString() { + return String.format("球号:%d,系数:%.4f,主球:%d", ballNumber, coefficient, masterBallNumber); + } + } + @Autowired private T3Mapper t3Mapper; @@ -82,6 +106,8 @@ public class BallAnalysisService { } List allNumbers = new ArrayList<>(); + // 第一步:记录球号和系数的对应关系 + List ballsWithCoefficients = new ArrayList<>(); // 第一步:处理6个红球,每个红球获取17个数字 log.info("第一步:处理6个红球,使用{}级别算法", level); @@ -89,11 +115,24 @@ public class BallAnalysisService { Integer redBall = redBalls.get(i); log.info("处理第{}个红球:{}", i + 1, redBall); - List ballNumbers = getTop17FromT3(redBall, level); + List ballsWithCoeffs = getTop17FromT3WithCoefficients(redBall, level); + ballsWithCoefficients.addAll(ballsWithCoeffs); + + List ballNumbers = ballsWithCoeffs.stream() + .map(BallWithCoefficient::getBallNumber) + .collect(Collectors.toList()); allNumbers.addAll(ballNumbers); log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers); } + + // 记录第一步的球号和系数统计 + log.info("=== 第一步球号和系数统计(共{}个) ===", ballsWithCoefficients.size()); + for (int i = 0; i < ballsWithCoefficients.size(); i++) { + BallWithCoefficient ballWithCoeff = ballsWithCoefficients.get(i); + log.info("第{}个:{}", i + 1, ballWithCoeff.toString()); + } + log.info("=== 第一步统计结束 ==="); // 第二步:从history_top获取前3个球号 log.info("第二步:从history_top获取前3个球号"); @@ -110,12 +149,56 @@ public class BallAnalysisService { log.info("总共收集到{}个数字", allNumbers.size()); // 第四步:统计频率并获取前11个 - List result = getTop11ByFrequency(allNumbers); + List result = getTop11ByFrequency(allNumbers, ballsWithCoefficients); log.info("球号分析算法完成,结果:{}", result); return result; } + /** + * 从T3表获取指定主球的17个从球号及其系数 + * 根据不同级别使用不同的选择策略 + * @param masterBallNumber 主球号 + * @param level 级别:H-高位,M-中位,L-低位 + * @return 球号和系数的对应关系列表 + */ + private List getTop17FromT3WithCoefficients(Integer masterBallNumber, String level) { + log.debug("从T3表查询主球{}的线系数数据(含系数),级别:{}", masterBallNumber, level); + + // 查询指定主球的所有数据,按线系数降序排列 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("masterBallNumber", masterBallNumber) + .orderByDesc("lineCoefficient") + .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 + + List t3List = t3Mapper.selectList(queryWrapper); + + if (t3List.isEmpty()) { + log.warn("T3表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + + switch (level) { + case "H": + // 高位:取前17个(按线系数从大到小排列) + result = getHighLevelBallsWithCoefficients(t3List, masterBallNumber); + break; + case "M": + // 中位:取线系数平均值向上9个球(含),向下8个球,共17个 + result = getMiddleLevelBallsWithCoefficients(t3List, masterBallNumber); + break; + case "L": + // 低位:取最小值向上17个球,含最小值 + result = getLowLevelBallsWithCoefficients(t3List, masterBallNumber); + break; + } + + log.debug("T3表主球{}{}级别最终选择的{}个从球(含系数):{}", masterBallNumber, level, result.size(), result); + return result; + } + /** * 从T3表获取指定主球的17个从球号 * 根据不同级别使用不同的选择策略 @@ -248,7 +331,61 @@ public class BallAnalysisService { } /** - * 中位算法:从T3表取线系数平均值向上9个球(含),向下8个球,共17个 + * 高位算法:从T3表取前17个(按线系数从大到小排列),包含系数信息 + */ + private List getHighLevelBallsWithCoefficients(List t3List, Integer masterBallNumber) { + if (t3List.size() < 17) { + log.warn("T3表数据不足17条,实际{}条", t3List.size()); + return t3List.stream() + .map(t3 -> new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)) + .collect(Collectors.toList()); + } + + // 获取前16个 + List result = new ArrayList<>(); + for (int i = 0; i < 16; i++) { + T3 t3 = t3List.get(i); + result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)); + } + + // 处理第17个位置:检查是否有相同的线系数 + Double targetCoefficient = t3List.get(16).getLineCoefficient(); + List candidatesFor17th = new ArrayList<>(); + + // 找出所有线系数等于第17个位置线系数的记录 + for (int i = 16; i < t3List.size(); i++) { + if (t3List.get(i).getLineCoefficient().equals(targetCoefficient)) { + candidatesFor17th.add(t3List.get(i)); + } else { + break; // 线系数不同,停止查找 + } + } + + if (candidatesFor17th.size() == 1) { + // 只有一个候选,直接添加 + T3 t3 = candidatesFor17th.get(0); + result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)); + } else { + // 有多个候选,通过history_top_100表的点系数排序 + log.debug("第17位有{}个相同线系数的候选:{}", candidatesFor17th.size(), + candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); + + Integer bestBall = selectBestBallFromHistoryTop100( + candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) + ); + // 找到对应的T3对象并添加 + T3 selectedT3 = candidatesFor17th.stream() + .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) + .findFirst() + .orElse(candidatesFor17th.get(0)); + result.add(new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); + } + + return result; + } + + /** + * 中位算法:从T3表取线系数平均值向上8个球,向下8个球,共17个 */ private List getMiddleLevelBalls(List t3List) { if (t3List.size() < 17) { @@ -264,20 +401,30 @@ public class BallAnalysisService { log.debug("T3表线系数平均值:{}", avgCoefficient); - // 找到最接近平均值的位置 + // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) int avgIndex = -1; double minDiff = Double.MAX_VALUE; for (int i = 0; i < t3List.size(); i++) { - double diff = Math.abs(t3List.get(i).getLineCoefficient() - avgCoefficient); - if (diff < minDiff) { + double coefficient = t3List.get(i).getLineCoefficient(); + // 只考虑大于平均值的值 + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 minDiff = diff; avgIndex = i; } } + } - log.debug("最接近平均值的位置:{},线系数:{}", avgIndex, t3List.get(avgIndex).getLineCoefficient()); + // 如果没有找到大于平均值的,则取最大值 + if (avgIndex == -1) { + avgIndex = 0; // T3表按线系数降序排列,第一个就是最大值 + log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t3List.get(avgIndex).getLineCoefficient()); + } else { + log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t3List.get(avgIndex).getLineCoefficient()); + } - // 向上9个(含当前),向下8个,共17个 + // 向上8个,向下8个,共17个 int startIndex = Math.max(0, avgIndex - 8); int endIndex = Math.min(t3List.size() - 1, avgIndex + 8); @@ -296,6 +443,119 @@ public class BallAnalysisService { result.add(t3List.get(i).getSlaveBallNumber()); } + // 处理边界位置的相同线系数情况 + result = handleT3BoundaryConflicts(t3List, result, startIndex, endIndex); + + log.debug("中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); + return result; + } + + /** + * 处理T3表边界位置的线系数冲突 + */ + private List handleT3BoundaryConflicts(List t3List, List candidates, int startIndex, int endIndex) { + List result = new ArrayList<>(candidates); + if (result.size() >= 17) { + // 检查第1个位置(最大线系数)的冲突 + Double firstCoefficient = t3List.get(startIndex).getLineCoefficient(); + List candidatesForFirst = new ArrayList<>(); + candidatesForFirst.add(t3List.get(startIndex)); // 边界本身 + for (int i = 0; i < startIndex; i++) { + if (t3List.get(i).getLineCoefficient().equals(firstCoefficient)) { + candidatesForFirst.add(t3List.get(i)); + } + } + if (candidatesForFirst.size() > 1) { + log.debug("第一位及其上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), + candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); + Integer bestBall = selectBestBallFromHistoryTop100( + candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) + ); + result.set(0, bestBall); + } + // 检查最后一个位置(最小线系数)的冲突 + Double lastCoefficient = t3List.get(endIndex).getLineCoefficient(); + List candidatesForLast = new ArrayList<>(); + candidatesForLast.add(t3List.get(endIndex)); // 边界本身 + for (int i = endIndex + 1; i < t3List.size(); i++) { + if (t3List.get(i).getLineCoefficient().equals(lastCoefficient)) { + candidatesForLast.add(t3List.get(i)); + } + } + if (candidatesForLast.size() > 1) { + log.debug("最后一位及其下面有{}个相同线系数的候选:{}", candidatesForLast.size(), + candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); + Integer bestBall = selectBestBallFromHistoryTop100( + candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) + ); + result.set(result.size() - 1, bestBall); + } + } + return result; + } + + /** + * 中位算法:从T3表取线系数平均值向上8个球,向下8个球,共17个,包含系数信息 + */ + private List getMiddleLevelBallsWithCoefficients(List t3List, Integer masterBallNumber) { + if (t3List.size() < 17) { + log.warn("T3表数据不足17条,实际{}条", t3List.size()); + return t3List.stream() + .map(t3 -> new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)) + .collect(Collectors.toList()); + } + + // 计算线系数平均值 + double avgCoefficient = t3List.stream() + .mapToDouble(T3::getLineCoefficient) + .average() + .orElse(0.0); + + log.debug("T3表线系数平均值:{}", avgCoefficient); + + // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) + int avgIndex = -1; + double minDiff = Double.MAX_VALUE; + for (int i = 0; i < t3List.size(); i++) { + double coefficient = t3List.get(i).getLineCoefficient(); + // 只考虑大于平均值的值 + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 + minDiff = diff; + avgIndex = i; + } + } + } + + // 如果没有找到大于平均值的,则取最大值 + if (avgIndex == -1) { + avgIndex = 0; // T3表按线系数降序排列,第一个就是最大值 + log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t3List.get(avgIndex).getLineCoefficient()); + } else { + log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t3List.get(avgIndex).getLineCoefficient()); + } + + // 向上8个,向下8个,共17个 + int startIndex = Math.max(0, avgIndex - 8); + int endIndex = Math.min(t3List.size() - 1, avgIndex + 8); + + // 确保总共17个数字 + while (endIndex - startIndex + 1 < 17 && (startIndex > 0 || endIndex < t3List.size() - 1)) { + if (startIndex > 0) { + startIndex--; + } + if (endIndex < t3List.size() - 1 && endIndex - startIndex + 1 < 17) { + endIndex++; + } + } + + List result = new ArrayList<>(); + for (int i = startIndex; i <= endIndex && result.size() < 17; i++) { + T3 t3 = t3List.get(i); + result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)); + } + log.debug("中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); return result; } @@ -347,6 +607,60 @@ public class BallAnalysisService { return candidates.subList(0, Math.min(17, candidates.size())); } + /** + * 低位算法:从T3表取最小值向上17个球,含最小值,包含系数信息 + */ + private List getLowLevelBallsWithCoefficients(List t3List, Integer masterBallNumber) { + if (t3List.size() < 17) { + log.warn("T3表数据不足17条,实际{}条", t3List.size()); + return t3List.stream() + .map(t3 -> new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)) + .collect(Collectors.toList()); + } + + // 从最后17个开始(最小的线系数) + int startIndex = Math.max(0, t3List.size() - 17); + List candidates = new ArrayList<>(); + + for (int i = startIndex; i < t3List.size(); i++) { + T3 t3 = t3List.get(i); + candidates.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)); + } + + // 处理第一个位置(最大线系数)的相同值情况 + if (candidates.size() >= 17) { + Double firstCoefficient = t3List.get(startIndex).getLineCoefficient(); + List candidatesForFirst = new ArrayList<>(); + + // 找出所有线系数等于第一个位置线系数的记录 + for (int i = 0; i < t3List.size(); i++) { + if (t3List.get(i).getLineCoefficient().equals(firstCoefficient)) { + candidatesForFirst.add(t3List.get(i)); + } + } + + if (candidatesForFirst.size() > 1) { + // 有多个候选,通过history_top_100表的点系数排序 + log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(), + candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); + + Integer bestBall = selectBestBallFromHistoryTop100( + candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) + ); + + // 找到对应的T3对象并替换第一个位置 + T3 selectedT3 = candidatesForFirst.stream() + .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) + .findFirst() + .orElse(candidatesForFirst.get(0)); + candidates.set(0, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); + } + } + + log.debug("低位算法选择范围:[{}, {}],共{}个球", startIndex, t3List.size() - 1, candidates.size()); + return candidates.subList(0, Math.min(17, candidates.size())); + } + /** * 高位算法:从T4表取前17个(按线系数从大到小排列) */ @@ -393,7 +707,7 @@ public class BallAnalysisService { } /** - * 中位算法:从T4表取线系数平均值向上9个球(含),向下8个球,共17个 + * 中位算法:从T4表取线系数平均值向上8个球,向下8个球,共17个 */ private List getMiddleLevelBallsFromT4(List t4List) { if (t4List.size() < 17) { @@ -409,20 +723,30 @@ public class BallAnalysisService { log.debug("T4表线系数平均值:{}", avgCoefficient); - // 找到最接近平均值的位置 + // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) int avgIndex = -1; double minDiff = Double.MAX_VALUE; for (int i = 0; i < t4List.size(); i++) { - double diff = Math.abs(t4List.get(i).getLineCoefficient() - avgCoefficient); - if (diff < minDiff) { + double coefficient = t4List.get(i).getLineCoefficient(); + // 只考虑大于平均值的值 + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 minDiff = diff; avgIndex = i; } } + } - log.debug("最接近平均值的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); + // 如果没有找到大于平均值的,则取最大值 + if (avgIndex == -1) { + avgIndex = 0; // T4表按线系数降序排列,第一个就是最大值 + log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); + } else { + log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); + } - // 向上9个(含当前),向下8个,共17个 + // 向上8个,向下8个,共17个 int startIndex = Math.max(0, avgIndex - 8); int endIndex = Math.min(t4List.size() - 1, avgIndex + 8); @@ -441,10 +765,73 @@ public class BallAnalysisService { result.add(t4List.get(i).getSlaveBallNumber()); } + // 处理边界位置的相同线系数情况 + result = handleT4BoundaryConflicts(t4List, result, startIndex, endIndex); + log.debug("中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); return result; } + /** + * 处理T4表边界位置的线系数冲突 + */ + private List handleT4BoundaryConflicts(List t4List, List candidates, + int startIndex, int endIndex) { + List result = new ArrayList<>(candidates); + + if (result.size() >= 17) { + // 检查第1个位置(最大线系数)的冲突 + Double firstCoefficient = t4List.get(startIndex).getLineCoefficient(); + List candidatesForFirst = new ArrayList<>(); + + // 找出第一个位置上面(如果存在)的所有相同线系数的记录 + for (int i = 0; i < startIndex; i++) { + if (t4List.get(i).getLineCoefficient().equals(firstCoefficient)) { + candidatesForFirst.add(t4List.get(i)); + } + } + + if (candidatesForFirst.size() > 0) { + // 有多个候选,通过history_top_100表的点系数排序 + log.debug("第一位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), + candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); + + Integer bestBall = selectBestBallFromHistoryTop100( + candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) + ); + + // 替换第一个位置 + result.set(0, bestBall); + } + + // 检查最后一个位置(最小线系数)的冲突 + Double lastCoefficient = t4List.get(endIndex).getLineCoefficient(); + List candidatesForLast = new ArrayList<>(); + + // 找出最后一个位置下面(如果存在)的所有相同线系数的记录 + for (int i = endIndex + 1; i < t4List.size(); i++) { + if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) { + candidatesForLast.add(t4List.get(i)); + } + } + + if (candidatesForLast.size() > 0) { + // 有多个候选,通过history_top_100表的点系数排序 + log.debug("最后一位下面有{}个相同线系数的候选:{}", candidatesForLast.size(), + candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); + + Integer bestBall = selectBestBallFromHistoryTop100( + candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) + ); + + // 替换最后一个位置 + result.set(result.size() - 1, bestBall); + } + } + + return result; + } + /** * 低位算法:从T4表取线系数最小值向上第4-14个球的10个球号 */ @@ -482,14 +869,15 @@ public class BallAnalysisService { Double firstCoefficient = t4List.get(startIndex).getLineCoefficient(); List candidatesForFirst = new ArrayList<>(); - for (int i = 0; i < t4List.size(); i++) { + // 找出第一个位置上面(如果存在)的所有相同线系数的记录 + for (int i = 0; i < startIndex; i++) { if (t4List.get(i).getLineCoefficient().equals(firstCoefficient)) { candidatesForFirst.add(t4List.get(i)); } } - if (candidatesForFirst.size() > 1) { - log.debug("第1位有{}个相同线系数的候选:{}", candidatesForFirst.size(), + if (candidatesForFirst.size() > 0) { + log.debug("第1位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( @@ -502,14 +890,15 @@ public class BallAnalysisService { Double lastCoefficient = t4List.get(endIndex).getLineCoefficient(); List candidatesForLast = new ArrayList<>(); - for (int i = 0; i < t4List.size(); i++) { + // 找出第10个位置下面(如果存在)的所有相同线系数的记录 + for (int i = endIndex + 1; i < t4List.size(); i++) { if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) { candidatesForLast.add(t4List.get(i)); } } - if (candidatesForLast.size() > 1) { - log.debug("第10位有{}个相同线系数的候选:{}", candidatesForLast.size(), + if (candidatesForLast.size() > 0) { + log.debug("第10位下面有{}个相同线系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( @@ -605,9 +994,13 @@ public class BallAnalysisService { /** * 统计数字出现频率,返回频率最高的前11个数字 - * 如果频次相同的球号超过11个,使用T3表线系数进行二次筛选 + * 如果频次相同的球号超过11个,使用多层筛选: + * 1. ballNumbersWithCoefficients系数和筛选 + * 2. history_top_100表排名筛选 + * 3. history_top表点系数筛选 + * 4. 随机选择 */ - private List getTop11ByFrequency(List allNumbers) { + private List getTop11ByFrequency(List allNumbers, List ballsWithCoefficients) { log.debug("统计{}个数字的出现频率", allNumbers.size()); // 统计频率 @@ -618,6 +1011,19 @@ public class BallAnalysisService { log.debug("数字频率统计:{}", frequencyMap); + // 打印所有球号出现频率的详细信息 + log.info("=== 所有球号出现频率统计 ==="); + List> sortedEntries = frequencyMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed() + .thenComparing(Map.Entry.comparingByKey())) + .collect(Collectors.toList()); + + for (int i = 0; i < sortedEntries.size(); i++) { + Map.Entry entry = sortedEntries.get(i); + log.info("第{}位:数字{},出现{}次", i + 1, entry.getKey(), entry.getValue()); + } + log.info("=== 频率统计结束 ==="); + // 按频率分组 Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : frequencyMap.entrySet()) { @@ -647,14 +1053,14 @@ public class BallAnalysisService { result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { - // 会超过11个,需要用T3表的线系数进行二次筛选 + // 会超过11个,需要从这组球号中选择部分 int remainingSlots = 11 - result.size(); - log.info("需要从{}个频率相同的球号中选择{}个,使用T3表线系数进行筛选", balls.size(), remainingSlots); + log.info("需要从{}个频率相同的球号中选择{}个,开始多层筛选", balls.size(), remainingSlots); - List selectedBalls = selectBallsByT3LineCoefficient(balls, remainingSlots); + List selectedBalls = selectBallsByMultiLevelFiltering(balls, remainingSlots, ballsWithCoefficients); result.addAll(selectedBalls); - log.info("通过T3表线系数筛选完成,最终选择:{}", selectedBalls); + log.info("多层筛选完成,最终选择:{}", selectedBalls); break; // 已经达到11个,结束 } @@ -677,312 +1083,239 @@ public class BallAnalysisService { } /** - * 根据T3表的线系数总和选择球号,支持多级筛选 - * @param candidateBalls 候选球号列表 - * @param selectCount 需要选择的数量 - * @return 选择的球号列表 + * 多层筛选:ballNumbersWithCoefficients系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择 */ - private List selectBallsByT3LineCoefficient(List candidateBalls, int selectCount) { - log.info("开始根据T3表线系数总和选择球号,候选:{},需要选择:{}", candidateBalls, selectCount); + private List selectBallsByMultiLevelFiltering(List candidateBalls, int selectCount, List ballsWithCoefficients) { + log.debug("开始多层筛选,候选球号:{},需要选择:{}个", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { - log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号"); return new ArrayList<>(candidateBalls); } + + List currentCandidates = new ArrayList<>(candidateBalls); + + // 第一层:ballNumbersWithCoefficients系数和筛选 + log.debug("=== 第一层:ballNumbersWithCoefficients系数和筛选 ==="); + List ballNumbersFiltered = selectBallsByBallNumbersWithCoefficients(currentCandidates, selectCount, ballsWithCoefficients); + log.debug("ballNumbersWithCoefficients系数和筛选结果:{}", ballNumbersFiltered); - // 第一级:计算每个球号作为主球时的线系数总和 - Map lineCoefficientSumMap = new HashMap<>(); + // 如果ballNumbersWithCoefficients筛选后的结果数量等于selectCount,说明没有相同系数和的情况,直接返回 + if (ballNumbersFiltered.size() == selectCount) { + log.debug("ballNumbersWithCoefficients系数和筛选完成,选择:{}", ballNumbersFiltered); + return ballNumbersFiltered; + } + // 如果ballNumbersWithCoefficients筛选后的结果数量超过selectCount,说明有系数和相同的情况,继续下一层筛选 + currentCandidates = ballNumbersFiltered; + log.debug("ballNumbersWithCoefficients筛选后剩余候选:{}", currentCandidates); + + // 第二层:history_top_100表排名筛选 + log.debug("=== 第二层:history_top_100表排名筛选 ==="); + List historyTop100Filtered = selectBallsByHistoryTop100Ranking(currentCandidates, selectCount); + log.debug("history_top_100表排名筛选结果:{}", historyTop100Filtered); + + if (historyTop100Filtered.size() == selectCount) { + log.debug("history_top_100表排名筛选完成,选择:{}", historyTop100Filtered); + return historyTop100Filtered; + } + currentCandidates = historyTop100Filtered; + log.debug("history_top_100表筛选后剩余候选:{}", currentCandidates); + + // 第三层:history_top表点系数筛选 + log.debug("=== 第三层:history_top表点系数筛选 ==="); + List historyTopFiltered = selectBallsByHistoryTopPointCoefficient(currentCandidates, selectCount); + log.debug("history_top表点系数筛选结果:{}", historyTopFiltered); + + if (historyTopFiltered.size() == selectCount) { + log.debug("history_top表点系数筛选完成,选择:{}", historyTopFiltered); + return historyTopFiltered; + } + currentCandidates = historyTopFiltered; + log.debug("history_top表筛选后剩余候选:{}", currentCandidates); + + // 第四层:随机选择 + log.debug("=== 第四层:随机选择 ==="); + List randomSelected = selectBallsRandomly(currentCandidates, selectCount); + log.debug("随机筛选完成,选择:{}", randomSelected); + return randomSelected; + } + + /** + * 根据ballNumbersWithCoefficients系数和进行筛选 + * 当系数和相同时,返回所有相同系数和的球号,让下一层筛选来处理 + */ + private List selectBallsByBallNumbersWithCoefficients(List candidateBalls, int selectCount, List ballsWithCoefficients) { + log.debug("使用ballNumbersWithCoefficients系数和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount); + + if (candidateBalls.size() <= selectCount) { + return new ArrayList<>(candidateBalls); + } + + // 计算每个球号的系数和 + Map ballCoefficientSum = new HashMap<>(); for (Integer ballNumber : candidateBalls) { + double coefficientSum = calculateBallNumbersWithCoefficientsSum(ballNumber, ballsWithCoefficients); + ballCoefficientSum.put(ballNumber, coefficientSum); + log.debug("球号{}的ballNumbersWithCoefficients系数和:{}", ballNumber, coefficientSum); + } + + // 按系数和分组(从高到低排序) + Map> coefficientGroups = new TreeMap<>(Collections.reverseOrder()); + for (Integer ballNumber : candidateBalls) { + double coefficientSum = ballCoefficientSum.get(ballNumber); + coefficientGroups.computeIfAbsent(coefficientSum, k -> new ArrayList<>()).add(ballNumber); + } + + log.debug("ballNumbersWithCoefficients系数分组:{}", coefficientGroups); + + List result = new ArrayList<>(); + + // 按系数和从高到低处理 + for (Map.Entry> group : coefficientGroups.entrySet()) { + Double coefficientSum = group.getKey(); + List balls = group.getValue(); + + log.debug("系数和{}的球号:{}", coefficientSum, balls); + + // 检查加入这组球号后是否会超过selectCount个 + if (result.size() + balls.size() <= selectCount) { + // 不会超过,直接添加所有球号(按球号升序排序) + Collections.sort(balls); + result.addAll(balls); + log.debug("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过,返回所有相同系数和的球号,让下一层筛选来处理 + result.addAll(balls); + log.debug("系数和相同,返回所有相同系数和的球号:{},让下一层筛选处理", balls); + break; + } + + // 如果已经有selectCount个,结束 + if (result.size() >= selectCount) { + break; + } + } + + log.debug("ballNumbersWithCoefficients筛选结果:{}", result); + return result; + } + + /** + * 计算指定球号在ballNumbersWithCoefficients中的系数和 + * 从ballsWithCoefficients列表中找到所有匹配的球号,计算系数和 + */ + private double calculateBallNumbersWithCoefficientsSum(Integer ballNumber, List ballsWithCoefficients) { + try { + // 从ballsWithCoefficients中找到所有匹配的球号,计算系数和 + double sum = ballsWithCoefficients.stream() + .filter(ball -> ball.getBallNumber().equals(ballNumber)) + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + + log.debug("球号{}在ballsWithCoefficients中的系数和:{}", ballNumber, sum); + return sum; + } catch (Exception e) { + log.error("计算ballNumbersWithCoefficients系数和时出错,球号:{}", ballNumber, e); + return 0.0; + } + } + + /** + * 根据T3表线系数之和进行筛选 + * 当线系数之和相同时,返回所有相同线系数之和的球号,让下一层筛选来处理 + */ + private List selectBallsByT3LineCoefficientSum(List candidateBalls, int selectCount) { + log.debug("使用T3表线系数之和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount); + + if (candidateBalls.size() <= selectCount) { + return new ArrayList<>(candidateBalls); + } + + // 计算每个球号作为主球号时的线系数之和 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ballNumber : candidateBalls) { + double coefficientSum = calculateT3LineCoefficientSum(ballNumber); + ballCoefficientSum.put(ballNumber, coefficientSum); + log.debug("球号{}的T3线系数之和:{}", ballNumber, coefficientSum); + } + + // 按线系数之和降序排序 + List sortedBalls = candidateBalls.stream() + .sorted((a, b) -> Double.compare(ballCoefficientSum.get(b), ballCoefficientSum.get(a))) + .collect(Collectors.toList()); + + log.debug("按T3线系数之和排序后的球号:{}", sortedBalls); + + // 按线系数之和分组 + Map> coefficientGroups = new LinkedHashMap<>(); + for (Integer ballNumber : sortedBalls) { + double coefficientSum = ballCoefficientSum.get(ballNumber); + coefficientGroups.computeIfAbsent(coefficientSum, k -> new ArrayList<>()).add(ballNumber); + } + + log.debug("T3表线系数分组:{}", coefficientGroups); + + List result = new ArrayList<>(); + + // 按线系数之和从高到低处理 + for (Map.Entry> group : coefficientGroups.entrySet()) { + Double coefficientSum = group.getKey(); + List balls = group.getValue(); + + log.debug("线系数之和{}的球号:{}", coefficientSum, balls); + + // 检查加入这组球号后是否会超过selectCount个 + if (result.size() + balls.size() <= selectCount) { + // 不会超过,直接添加所有球号(按球号升序排序) + Collections.sort(balls); + result.addAll(balls); + log.debug("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过,返回所有相同线系数之和的球号,让下一层筛选来处理 + result.addAll(balls); + log.debug("线系数之和相同,返回所有相同线系数的球号:{},让下一层筛选处理", balls); + break; + } + + // 如果已经有selectCount个,结束 + if (result.size() >= selectCount) { + break; + } + } + + log.debug("T3表线系数筛选结果:{}", result); + return result; + } + + /** + * 计算指定球号作为主球号时的T3表线系数之和 + */ + private double calculateT3LineCoefficientSum(Integer masterBallNumber) { + try { + // 查询T3表中该主球号的所有记录 QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("masterBallNumber", ballNumber); + queryWrapper.eq("masterBallNumber", masterBallNumber) + .orderByDesc("lineCoefficient"); List t3List = t3Mapper.selectList(queryWrapper); + if (t3List.isEmpty()) { + log.debug("T3表中主球{}没有数据,线系数之和为0", masterBallNumber); + return 0.0; + } + + // 计算线系数之和 double sum = t3List.stream() .mapToDouble(T3::getLineCoefficient) .sum(); - lineCoefficientSumMap.put(ballNumber, sum); - log.debug("球号{}作为主球的线系数总和:{}", ballNumber, sum); - } - - // 按线系数总和分组 - Map> lineCoefficientGroups = new TreeMap<>(Collections.reverseOrder()); - for (Map.Entry entry : lineCoefficientSumMap.entrySet()) { - Double sum = entry.getValue(); - Integer ballNumber = entry.getKey(); - lineCoefficientGroups.computeIfAbsent(sum, k -> new ArrayList<>()).add(ballNumber); - } - - log.info("T3表线系数总和分组:{}", lineCoefficientGroups); - - List result = new ArrayList<>(); - - // 按线系数总和从高到低处理 - for (Map.Entry> group : lineCoefficientGroups.entrySet()) { - Double lineSum = group.getKey(); - List balls = group.getValue(); + log.debug("T3表主球{}的线系数之和:{}", masterBallNumber, sum); + return sum; - log.info("线系数总和{}的球号:{}", lineSum, balls); - - // 检查加入这组球号后是否会超过selectCount个 - if (result.size() + balls.size() <= selectCount) { - // 不会超过,直接添加所有球号(按球号升序排序) - Collections.sort(balls); - result.addAll(balls); - log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); - } else { - // 会超过,需要进一步筛选 - int remainingSlots = selectCount - result.size(); - log.info("需要从{}个线系数总和相同的球号中选择{}个,开始多级筛选", balls.size(), remainingSlots); - - List selectedBalls = selectBallsByMultiLevelFiltering(balls, remainingSlots); - result.addAll(selectedBalls); - - log.info("通过多级筛选完成,最终选择:{}", selectedBalls); - break; // 已经达到selectCount个,结束 - } - - // 如果已经有selectCount个,结束 - if (result.size() >= selectCount) { - break; - } + } catch (Exception e) { + log.error("计算T3表线系数之和时出错,主球号:{}", masterBallNumber, e); + return 0.0; } - - log.info("T3表线系数筛选最终结果(共{}个):{}", result.size(), result); - - // 打印详细的线系数信息 - for (int i = 0; i < result.size(); i++) { - Integer ballNumber = result.get(i); - Double sum = lineCoefficientSumMap.get(ballNumber); - log.info("第{}位:球号{},线系数总和:{}", i + 1, ballNumber, sum); - } - - return result; - } - - /** - * 多级筛选球号:history_top_100 -> history_top -> 随机选择 - * @param candidateBalls 候选球号列表 - * @param selectCount 需要选择的数量 - * @return 选择的球号列表 - */ - private List selectBallsByMultiLevelFiltering(List candidateBalls, int selectCount) { - log.info("开始多级筛选,候选:{},需要选择:{}", candidateBalls, selectCount); - - if (candidateBalls.size() <= selectCount) { - log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号"); - return new ArrayList<>(candidateBalls); - } - - // 第二级:使用history_top_100表的排名进行筛选 - List filteredByTop100 = selectBallsByHistoryTop100Ranking(candidateBalls, selectCount); - - if (filteredByTop100.size() == selectCount) { - log.info("通过history_top_100表筛选成功,结果:{}", filteredByTop100); - return filteredByTop100; - } - - // 第三级:使用history_top表的点系数进行筛选 - log.info("history_top_100表筛选后仍有{}个球号,继续使用history_top表筛选", filteredByTop100.size()); - List filteredByTop = selectBallsByHistoryTopPointCoefficient(filteredByTop100, selectCount); - - if (filteredByTop.size() == selectCount) { - log.info("通过history_top表筛选成功,结果:{}", filteredByTop); - return filteredByTop; - } - - // 第四级:随机选择 - log.info("history_top表筛选后仍有{}个球号,进行随机选择", filteredByTop.size()); - List finalResult = selectBallsRandomly(filteredByTop, selectCount); - - log.info("随机选择完成,最终结果:{}", finalResult); - return finalResult; - } - - /** - * 根据history_top_100表的排名筛选球号 - * @param candidateBalls 候选球号列表 - * @param selectCount 需要选择的数量 - * @return 筛选后的球号列表 - */ - private List selectBallsByHistoryTop100Ranking(List candidateBalls, int selectCount) { - log.info("使用history_top_100表排名筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount); - - // 查询这些球号在history_top_100表中的点系数 - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.in("ballNumber", candidateBalls) - .orderByDesc("pointCoefficient") - .orderByAsc("ballNumber"); // 点系数相同时按球号升序 - - List top100List = historyTop100Mapper.selectList(queryWrapper); - - if (top100List.isEmpty()) { - log.warn("候选球号{}在history_top_100表中未找到数据,按球号升序返回", candidateBalls); - Collections.sort(candidateBalls); - return candidateBalls.subList(0, Math.min(selectCount, candidateBalls.size())); - } - - // 按点系数分组 - Map> pointCoefficientGroups = new LinkedHashMap<>(); - for (HistoryTop100 item : top100List) { - Double pointCoefficient = item.getPointCoefficient(); - Integer ballNumber = item.getBallNumber(); - pointCoefficientGroups.computeIfAbsent(pointCoefficient, k -> new ArrayList<>()).add(ballNumber); - } - - log.info("history_top_100表点系数分组:{}", pointCoefficientGroups); - - List result = new ArrayList<>(); - - // 按点系数从高到低处理 - for (Map.Entry> group : pointCoefficientGroups.entrySet()) { - Double pointCoefficient = group.getKey(); - List balls = group.getValue(); - - log.info("点系数{}的球号:{}", pointCoefficient, balls); - - // 检查加入这组球号后是否会超过selectCount个 - if (result.size() + balls.size() <= selectCount) { - // 不会超过,直接添加所有球号(按球号升序排序) - Collections.sort(balls); - result.addAll(balls); - log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); - } else { - // 会超过,按球号升序选择需要的数量 - int remainingSlots = selectCount - result.size(); - Collections.sort(balls); - result.addAll(balls.subList(0, remainingSlots)); - log.info("选择前{}个球号:{}", remainingSlots, balls.subList(0, remainingSlots)); - break; - } - - // 如果已经有selectCount个,结束 - if (result.size() >= selectCount) { - break; - } - } - - // 处理不在history_top_100表中的球号 - List notInTop100 = new ArrayList<>(candidateBalls); - notInTop100.removeAll(result); - if (!notInTop100.isEmpty() && result.size() < selectCount) { - int remainingSlots = selectCount - result.size(); - Collections.sort(notInTop100); - int addCount = Math.min(remainingSlots, notInTop100.size()); - result.addAll(notInTop100.subList(0, addCount)); - log.info("添加不在history_top_100表中的{}个球号:{}", addCount, notInTop100.subList(0, addCount)); - } - - log.info("history_top_100表筛选结果:{}", result); - return result; - } - - /** - * 根据history_top表的点系数筛选球号 - * @param candidateBalls 候选球号列表 - * @param selectCount 需要选择的数量 - * @return 筛选后的球号列表 - */ - private List selectBallsByHistoryTopPointCoefficient(List candidateBalls, int selectCount) { - log.info("使用history_top表点系数筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount); - - // 查询这些球号在history_top表中的点系数 - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.in("ballNumber", candidateBalls) - .orderByDesc("pointCoefficient") - .orderByAsc("ballNumber"); // 点系数相同时按球号升序 - - List historyTopList = historyTopMapper.selectList(queryWrapper); - - if (historyTopList.isEmpty()) { - log.warn("候选球号{}在history_top表中未找到数据,按球号升序返回", candidateBalls); - Collections.sort(candidateBalls); - return candidateBalls.subList(0, Math.min(selectCount, candidateBalls.size())); - } - - // 按点系数分组 - Map> pointCoefficientGroups = new LinkedHashMap<>(); - for (HistoryTop item : historyTopList) { - Double pointCoefficient = item.getPointCoefficient(); - Integer ballNumber = item.getBallNumber(); - pointCoefficientGroups.computeIfAbsent(pointCoefficient, k -> new ArrayList<>()).add(ballNumber); - } - - log.info("history_top表点系数分组:{}", pointCoefficientGroups); - - List result = new ArrayList<>(); - - // 按点系数从高到低处理 - for (Map.Entry> group : pointCoefficientGroups.entrySet()) { - Double pointCoefficient = group.getKey(); - List balls = group.getValue(); - - log.info("点系数{}的球号:{}", pointCoefficient, balls); - - // 检查加入这组球号后是否会超过selectCount个 - if (result.size() + balls.size() <= selectCount) { - // 不会超过,直接添加所有球号(按球号升序排序) - Collections.sort(balls); - result.addAll(balls); - log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); - } else { - // 会超过,按球号升序选择需要的数量 - int remainingSlots = selectCount - result.size(); - Collections.sort(balls); - result.addAll(balls.subList(0, remainingSlots)); - log.info("选择前{}个球号:{}", remainingSlots, balls.subList(0, remainingSlots)); - break; - } - - // 如果已经有selectCount个,结束 - if (result.size() >= selectCount) { - break; - } - } - - // 处理不在history_top表中的球号 - List notInHistoryTop = new ArrayList<>(candidateBalls); - notInHistoryTop.removeAll(result); - if (!notInHistoryTop.isEmpty() && result.size() < selectCount) { - int remainingSlots = selectCount - result.size(); - Collections.sort(notInHistoryTop); - int addCount = Math.min(remainingSlots, notInHistoryTop.size()); - result.addAll(notInHistoryTop.subList(0, addCount)); - log.info("添加不在history_top表中的{}个球号:{}", addCount, notInHistoryTop.subList(0, addCount)); - } - - log.info("history_top表筛选结果:{}", result); - return result; - } - - /** - * 随机选择球号 - * @param candidateBalls 候选球号列表 - * @param selectCount 需要选择的数量 - * @return 随机选择的球号列表 - */ - private List selectBallsRandomly(List candidateBalls, int selectCount) { - log.info("随机选择球号,候选:{},需要选择:{}", candidateBalls, selectCount); - - if (candidateBalls.size() <= selectCount) { - log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号"); - return new ArrayList<>(candidateBalls); - } - - // 创建候选球号的副本并打乱顺序 - List shuffledBalls = new ArrayList<>(candidateBalls); - Collections.shuffle(shuffledBalls, new Random(System.currentTimeMillis())); - - // 选择前selectCount个 - List result = shuffledBalls.subList(0, selectCount); - - // 按球号升序排序结果 - Collections.sort(result); - - log.info("随机选择结果:{}", result); - return result; } /** @@ -1047,9 +1380,9 @@ public class BallAnalysisService { allNumbers.addAll(lastTwoOfFirstThree); log.info("前3个红球的后两个:{}", lastTwoOfFirstThree); - // 第五步:用上期蓝球从T4表获取17个蓝球号码 - log.info("第五步:用上期蓝球{}从T4表获取17个蓝球号码", blueBall); - List blueNumbers = getTop17FromT4(blueBall); + // 第五步:用上期蓝球从T4表获取26个蓝球号码 + log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", blueBall); + List blueNumbers = getTop26FromT4(blueBall); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers); @@ -1188,7 +1521,7 @@ public class BallAnalysisService { } /** - * 中位算法:从T7表取面系数平均值向上6个向下4个的10个球号 + * 中位算法:从T7表取面系数平均值向上5个向下5个的10个球号 */ private List getMiddleLevelBallsFromT7(List t7List) { if (t7List.size() < 10) { @@ -1204,22 +1537,32 @@ public class BallAnalysisService { log.debug("T7表面系数平均值:{}", avgCoefficient); - // 找到最接近平均值的位置 - int avgIndex = 0; + // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) + int avgIndex = -1; double minDiff = Double.MAX_VALUE; for (int i = 0; i < t7List.size(); i++) { - double diff = Math.abs(t7List.get(i).getFaceCoefficient() - avgCoefficient); - if (diff < minDiff) { + double coefficient = t7List.get(i).getFaceCoefficient(); + // 只考虑大于平均值的值 + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 minDiff = diff; avgIndex = i; } } + } - log.debug("最接近平均值的位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient()); + // 如果没有找到大于平均值的,则取最大值 + if (avgIndex == -1) { + avgIndex = 0; // T7表按面系数降序排列,第一个就是最大值 + log.debug("未找到大于平均值的位置,使用最大值位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient()); + } else { + log.debug("找到大于平均值且最接近的位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient()); + } - // 向上6个(含当前),向下4个,共10个 - int startIndex = Math.max(0, avgIndex - 4); - int endIndex = Math.min(t7List.size() - 1, avgIndex + 5); + // 向上5个,向下5个,共10个 + int startIndex = Math.max(0, avgIndex - 5); + int endIndex = Math.min(t7List.size() - 1, avgIndex + 4); // 确保总共10个数字 while (endIndex - startIndex + 1 < 10 && (startIndex > 0 || endIndex < t7List.size() - 1)) { @@ -1280,14 +1623,15 @@ public class BallAnalysisService { Double firstCoefficient = t7List.get(startIndex).getFaceCoefficient(); List candidatesForFirst = new ArrayList<>(); - for (int i = 0; i < t7List.size(); i++) { + // 找出第一个位置上面(如果存在)的所有相同面系数的记录 + for (int i = 0; i < startIndex; i++) { if (t7List.get(i).getFaceCoefficient().equals(firstCoefficient)) { candidatesForFirst.add(t7List.get(i)); } } - if (candidatesForFirst.size() > 1) { - log.debug("第1位有{}个相同面系数的候选:{}", candidatesForFirst.size(), + if (candidatesForFirst.size() > 0) { + log.debug("第1位上面有{}个相同面系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( @@ -1300,14 +1644,15 @@ public class BallAnalysisService { Double lastCoefficient = t7List.get(endIndex).getFaceCoefficient(); List candidatesForLast = new ArrayList<>(); - for (int i = 0; i < t7List.size(); i++) { + // 找出第10个位置下面(如果存在)的所有相同面系数的记录 + for (int i = endIndex + 1; i < t7List.size(); i++) { if (t7List.get(i).getFaceCoefficient().equals(lastCoefficient)) { candidatesForLast.add(t7List.get(i)); } } - if (candidatesForLast.size() > 1) { - log.debug("第10位有{}个相同面系数的候选:{}", candidatesForLast.size(), + if (candidatesForLast.size() > 0) { + log.debug("第10位下面有{}个相同面系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( @@ -1390,7 +1735,8 @@ public class BallAnalysisService { // 查询前3个点系数最高的球号 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("pointCoefficient") - .orderByAsc("ballNumber"); // 点系数相同时按球号升序 + .orderByAsc("ballNumber") // 点系数相同时按球号升序 + .last("LIMIT 3"); // 限制只返回前3条记录 List top100List = historyTop100Mapper.selectList(queryWrapper); @@ -1496,7 +1842,7 @@ public class BallAnalysisService { } /** - * 中位算法:从T4表取线系数平均值向上6个向下4个的10个球号 + * 中位算法:从T4表取线系数平均值向上5个向下5个的10个球号 */ private List getMiddleLevelBallsFromT4For10(List t4List) { if (t4List.size() < 10) { @@ -1512,22 +1858,32 @@ public class BallAnalysisService { log.debug("T4表线系数平均值:{}", avgCoefficient); - // 找到最接近平均值的位置 - int avgIndex = 0; + // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) + int avgIndex = -1; double minDiff = Double.MAX_VALUE; for (int i = 0; i < t4List.size(); i++) { - double diff = Math.abs(t4List.get(i).getLineCoefficient() - avgCoefficient); - if (diff < minDiff) { + double coefficient = t4List.get(i).getLineCoefficient(); + // 只考虑大于平均值的值 + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 minDiff = diff; avgIndex = i; } } + } - log.debug("最接近平均值的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); + // 如果没有找到大于平均值的,则取最大值 + if (avgIndex == -1) { + avgIndex = 0; // T4表按线系数降序排列,第一个就是最大值 + log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); + } else { + log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); + } - // 向上6个(含当前),向下4个,共10个 - int startIndex = Math.max(0, avgIndex - 4); - int endIndex = Math.min(t4List.size() - 1, avgIndex + 5); + // 向上5个,向下5个,共10个 + int startIndex = Math.max(0, avgIndex - 5); + int endIndex = Math.min(t4List.size() - 1, avgIndex + 4); // 确保总共10个数字 while (endIndex - startIndex + 1 < 10 && (startIndex > 0 || endIndex < t4List.size() - 1)) { @@ -1750,7 +2106,7 @@ public class BallAnalysisService { } // 第二级:使用history_top_100表的排名进行筛选 - List filteredByTop100 = selectBallsByHistoryTop100RankingFor8(candidateBalls, selectCount); + List filteredByTop100 = selectBallsByHistoryTop100Ranking(candidateBalls, selectCount); if (filteredByTop100.size() == selectCount) { log.info("通过history_top_100表筛选成功,结果:{}", filteredByTop100); @@ -1759,7 +2115,7 @@ public class BallAnalysisService { // 第三级:使用history_top表的点系数进行筛选 log.info("history_top_100表筛选后仍有{}个球号,继续使用history_top表筛选", filteredByTop100.size()); - List filteredByTop = selectBallsByHistoryTopPointCoefficientFor8(filteredByTop100, selectCount); + List filteredByTop = selectBallsByHistoryTopPointCoefficient(filteredByTop100, selectCount); if (filteredByTop.size() == selectCount) { log.info("通过history_top表筛选成功,结果:{}", filteredByTop); @@ -1768,20 +2124,20 @@ public class BallAnalysisService { // 第四级:随机选择 log.info("history_top表筛选后仍有{}个球号,进行随机选择", filteredByTop.size()); - List finalResult = selectBallsRandomlyFor8(filteredByTop, selectCount); + List finalResult = selectBallsRandomly(filteredByTop, selectCount); log.info("随机选择完成,最终结果:{}", finalResult); return finalResult; } /** - * 根据history_top_100表的排名筛选球号(8个球版本) + * 根据history_top_100表的排名筛选球号 * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @return 筛选后的球号列表 */ - private List selectBallsByHistoryTop100RankingFor8(List candidateBalls, int selectCount) { - log.info("使用history_top_100表排名筛选球号(8个球版本),候选:{},需要选择:{}", candidateBalls, selectCount); + private List selectBallsByHistoryTop100Ranking(List candidateBalls, int selectCount) { + log.info("使用history_top_100表排名筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount); // 查询这些球号在history_top_100表中的点系数 QueryWrapper queryWrapper = new QueryWrapper<>(); @@ -1823,11 +2179,9 @@ public class BallAnalysisService { result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { - // 会超过,按球号升序选择需要的数量 - int remainingSlots = selectCount - result.size(); - Collections.sort(balls); - result.addAll(balls.subList(0, remainingSlots)); - log.info("选择前{}个球号:{}", remainingSlots, balls.subList(0, remainingSlots)); + // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 + result.addAll(balls); + log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); break; } @@ -1853,13 +2207,11 @@ public class BallAnalysisService { } /** - * 根据history_top表的点系数筛选球号(8个球版本) - * @param candidateBalls 候选球号列表 - * @param selectCount 需要选择的数量 - * @return 筛选后的球号列表 + * 根据history_top表的点系数筛选球号 + * 当点系数相同时,返回所有相同点系数的球号,让下一层筛选来处理 */ - private List selectBallsByHistoryTopPointCoefficientFor8(List candidateBalls, int selectCount) { - log.info("使用history_top表点系数筛选球号(8个球版本),候选:{},需要选择:{}", candidateBalls, selectCount); + private List selectBallsByHistoryTopPointCoefficient(List candidateBalls, int selectCount) { + log.info("使用history_top表点系数筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount); // 查询这些球号在history_top表中的点系数 QueryWrapper queryWrapper = new QueryWrapper<>(); @@ -1901,11 +2253,9 @@ public class BallAnalysisService { result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { - // 会超过,按球号升序选择需要的数量 - int remainingSlots = selectCount - result.size(); - Collections.sort(balls); - result.addAll(balls.subList(0, remainingSlots)); - log.info("选择前{}个球号:{}", remainingSlots, balls.subList(0, remainingSlots)); + // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 + result.addAll(balls); + log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); break; } @@ -1931,13 +2281,13 @@ public class BallAnalysisService { } /** - * 随机选择球号(8个球版本) + * 随机选择球号 * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @return 随机选择的球号列表 */ - private List selectBallsRandomlyFor8(List candidateBalls, int selectCount) { - log.info("随机选择球号(8个球版本),候选:{},需要选择:{}", candidateBalls, selectCount); + private List selectBallsRandomly(List candidateBalls, int selectCount) { + log.info("随机选择球号,候选:{},需要选择:{}", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号"); @@ -2012,11 +2362,11 @@ public class BallAnalysisService { log.info("第四步:添加预测的2个蓝球号码:{}", predictedBlueBalls); allNumbers.addAll(predictedBlueBalls); - // 第五步:用上期蓝球从T5表获取最大值向下12个蓝球号码 - log.info("第五步:用上期蓝球{}从T5表获取最大值向下12个蓝球号码", lastBlueBall); - List t5Numbers = getTop12FromT5(lastBlueBall); - allNumbers.addAll(t5Numbers); - log.info("上期蓝球{}从T5表获取到{}个蓝球号码:{}", lastBlueBall, t5Numbers.size(), t5Numbers); + // 第五步:用上期蓝球从T4表获取26个蓝球号码 + log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", lastBlueBall); + List blueNumbers = getTop26FromT4(lastBlueBall); + allNumbers.addAll(blueNumbers); + log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个蓝球号码", allNumbers.size()); diff --git a/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java b/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java index 7b16505..3411da8 100644 --- a/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java +++ b/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java @@ -21,7 +21,7 @@ public class PredictResultTask { * cron表达式:秒 分 时 日 月 周 * 0 0/5 * * * ? 表示每5分钟执行一次 */ - @Scheduled(cron = "0 0/1 * * * ?") + @Scheduled(cron = "0 0/10 * * * ?") public void processPendingPredictions() { try { log.info("=== 开始执行预测结果匹配定时任务 ==="); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bba2d5c..b6fd6e6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,8 +4,8 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cpzs - username: cpzs_root - password: cpzs_123456 + username: root + password: root # datasource: # driver-class-name: com.mysql.cj.jdbc.Driver # url: jdbc:mysql://47.117.22.239:3306/cpzs?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&connectTimeout=10000&socketTimeout=30000 @@ -19,17 +19,17 @@ spring: # max-lifetime: 1800000 # 连接的最大生命周期(毫秒) # auto-commit: true # 自动提交 # connection-test-query: SELECT 1 # 测试连接是否可用的查询语句 -# data: -# redis: -# host: localhost -# port: 6379 -# database: 0 data: redis: - host: 47.117.22.239 + host: localhost port: 6379 database: 0 - password: cpzs_123456 +# data: +# redis: +# host: 47.117.22.239 +# port: 6379 +# database: 0 +# password: cpzs_123456 server: port: 8123 servlet: