diff --git a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java index 414ce9c..8cabc11 100644 --- a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java +++ b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java @@ -84,6 +84,53 @@ public class BallAnalysisService { @Autowired private LotteryDrawsService lotteryDrawsService; + /** + * 辅助:查询给定主球在T7表中指定从球集合的面系数,返回BallWithCoefficient列表 + */ + private List getT7CoefficientsFor(Integer masterBallNumber, List slaveBallNumbers) { + if (slaveBallNumbers == null || slaveBallNumbers.isEmpty()) { + return new ArrayList<>(); + } + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("masterBallNumber", masterBallNumber) + .in("slaveBallNumber", slaveBallNumbers); + List rows = t7Mapper.selectList(qw); + // 用球号->系数映射,避免顺序问题 + Map num2Coeff = rows.stream() + .collect(Collectors.toMap(T7::getSlaveBallNumber, T7::getFaceCoefficient, (a,b)->a)); + List result = new ArrayList<>(); + for (Integer num : slaveBallNumbers) { + Double coeff = num2Coeff.get(num); + if (coeff != null) { + result.add(new BallWithCoefficient(num, coeff, masterBallNumber)); + } + } + return result; + } + + /** + * 辅助:查询给定主球在T8表中指定从球集合的面系数,返回BallWithCoefficient列表 + */ + private List getT8CoefficientsFor(Integer masterBallNumber, List slaveBallNumbers) { + if (slaveBallNumbers == null || slaveBallNumbers.isEmpty()) { + return new ArrayList<>(); + } + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("masterBallNumber", masterBallNumber) + .in("slaveBallNumber", slaveBallNumbers); + List rows = t8Mapper.selectList(qw); + Map num2Coeff = rows.stream() + .collect(Collectors.toMap(T8::getSlaveBallNumber, T8::getFaceCoefficient, (a,b)->a)); + List result = new ArrayList<>(); + for (Integer num : slaveBallNumbers) { + Double coeff = num2Coeff.get(num); + if (coeff != null) { + result.add(new BallWithCoefficient(num, coeff, masterBallNumber)); + } + } + return result; + } + /** * 球号分析算法主方法 * @param level 高位/中位/低位标识 (H/M/L) @@ -341,44 +388,50 @@ public class BallAnalysisService { .collect(Collectors.toList()); } - // 获取前16个 + // 获取前17个 List result = new ArrayList<>(); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < 17 && i < t3List.size(); 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())); + // 处理第17个位置(系数最小的球号)的毛边情况 + if (result.size() == 17) { + int lastIndex = 16; // 第17个位置的索引 + Double lastCoefficient = result.get(lastIndex).getCoefficient(); - 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)); + // 查找第17个位置向下相同系数值的球号 + List candidatesFor17th = new ArrayList<>(); + + // 添加第17个位置本身 + candidatesFor17th.add(t3List.get(16)); + + // 向下查找相同系数值的球号 + for (int i = 17; i < t3List.size(); i++) { + if (t3List.get(i).getLineCoefficient().equals(lastCoefficient)) { + candidatesFor17th.add(t3List.get(i)); + } else { + break; // 系数不同就停止查找 + } + } + + if (candidatesFor17th.size() > 1) { + 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对象并替换第17个位置 + T3 selectedT3 = candidatesFor17th.stream() + .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) + .findFirst() + .orElse(candidatesFor17th.get(0)); + result.set(lastIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); + + log.debug("第17个位置毛边处理完成,最终选择:{}", bestBall); + } } return result; @@ -556,6 +609,89 @@ public class BallAnalysisService { result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)); } + // 处理边界毛边情况 + if (result.size() >= 8) { + // 处理向上8个球号的第8个位置(索引为7)的毛边情况 + int upBoundaryIndex = 7; // 向上第8个球号的索引 + if (upBoundaryIndex < result.size()) { + Double upBoundaryCoefficient = result.get(upBoundaryIndex).getCoefficient(); + + // 查找向上还有没有相同系数值的球号 + List upCandidates = new ArrayList<>(); + int actualUpIndex = startIndex + upBoundaryIndex; + + // 添加边界球号本身 + upCandidates.add(t3List.get(actualUpIndex)); + + // 向上查找相同系数值的球号 + for (int i = actualUpIndex - 1; i >= 0; i--) { + if (t3List.get(i).getLineCoefficient().equals(upBoundaryCoefficient)) { + upCandidates.add(t3List.get(i)); + } else { + break; // 系数不同就停止查找 + } + } + + if (upCandidates.size() > 1) { + log.debug("向上第8个球号及其上面有{}个相同线系数的候选:{}", upCandidates.size(), + upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); + + Integer bestBall = selectBestBallFromHistoryTop100( + upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) + ); + + // 找到对应的T3对象并替换第8个位置 + T3 selectedT3 = upCandidates.stream() + .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) + .findFirst() + .orElse(upCandidates.get(0)); + result.set(upBoundaryIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); + + log.debug("向上第8个球号毛边处理完成,最终选择:{}", bestBall); + } + } + + // 处理向下8个球号的第8个位置的毛边情况 + int downBoundaryIndex = Math.min(16, result.size() - 1); // 向下第8个球号的索引(从中心点开始算) + if (downBoundaryIndex >= 8 && downBoundaryIndex < result.size()) { + Double downBoundaryCoefficient = result.get(downBoundaryIndex).getCoefficient(); + + // 查找向下还有没有相同系数值的球号 + List downCandidates = new ArrayList<>(); + int actualDownIndex = startIndex + downBoundaryIndex; + + // 添加边界球号本身 + downCandidates.add(t3List.get(actualDownIndex)); + + // 向下查找相同系数值的球号 + for (int i = actualDownIndex + 1; i < t3List.size(); i++) { + if (t3List.get(i).getLineCoefficient().equals(downBoundaryCoefficient)) { + downCandidates.add(t3List.get(i)); + } else { + break; // 系数不同就停止查找 + } + } + + if (downCandidates.size() > 1) { + log.debug("向下第8个球号及其下面有{}个相同线系数的候选:{}", downCandidates.size(), + downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); + + Integer bestBall = selectBestBallFromHistoryTop100( + downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) + ); + + // 找到对应的T3对象并替换相应位置 + T3 selectedT3 = downCandidates.stream() + .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) + .findFirst() + .orElse(downCandidates.get(0)); + result.set(downBoundaryIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); + + log.debug("向下第8个球号毛边处理完成,最终选择:{}", bestBall); + } + } + } + log.debug("中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); return result; } @@ -627,33 +763,43 @@ public class BallAnalysisService { candidates.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)); } - // 处理第一个位置(最大线系数)的相同值情况 - if (candidates.size() >= 17) { - Double firstCoefficient = t3List.get(startIndex).getLineCoefficient(); - List candidatesForFirst = new ArrayList<>(); + // 处理第17个位置(系数最大的球号)的毛边情况 + if (candidates.size() == 17) { + int lastIndex = 16; // 第17个位置的索引 + Double lastCoefficient = candidates.get(lastIndex).getCoefficient(); + int actualLastIndex = startIndex + lastIndex; - // 找出所有线系数等于第一个位置线系数的记录 - for (int i = 0; i < t3List.size(); i++) { - if (t3List.get(i).getLineCoefficient().equals(firstCoefficient)) { - candidatesForFirst.add(t3List.get(i)); + // 查找第17个位置向上相同系数值的球号 + List candidatesFor17th = new ArrayList<>(); + + // 添加第17个位置本身 + candidatesFor17th.add(t3List.get(actualLastIndex)); + + // 向上查找相同系数值的球号 + for (int i = actualLastIndex - 1; i >= 0; i--) { + if (t3List.get(i).getLineCoefficient().equals(lastCoefficient)) { + candidatesFor17th.add(t3List.get(i)); + } else { + break; // 系数不同就停止查找 } } - - if (candidatesForFirst.size() > 1) { - // 有多个候选,通过history_top_100表的点系数排序 - log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(), - candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); + + if (candidatesFor17th.size() > 1) { + log.debug("第17个位置及其向上有{}个相同线系数的候选:{}", candidatesFor17th.size(), + candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( - candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) + candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); - // 找到对应的T3对象并替换第一个位置 - T3 selectedT3 = candidatesForFirst.stream() + // 找到对应的T3对象并替换第17个位置 + T3 selectedT3 = candidatesFor17th.stream() .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) .findFirst() - .orElse(candidatesForFirst.get(0)); - candidates.set(0, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); + .orElse(candidatesFor17th.get(0)); + candidates.set(lastIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); + + log.debug("第17个位置毛边处理完成,最终选择:{}", bestBall); } } @@ -833,28 +979,60 @@ public class BallAnalysisService { } /** - * 低位算法:从T4表取线系数最小值向上第4-14个球的10个球号 + * 低位算法:从T4表取最小值向上17个球,含最小值,共17个球号 */ private List getLowLevelBallsFromT4(List t4List) { - if (t4List.size() < 14) { - log.warn("T4表数据不足14条,实际{}条", t4List.size()); + if (t4List.size() < 17) { + log.warn("T4表数据不足17条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } - // 从最后14个开始,取第4-14个(即倒数第11到倒数第1个) - int startIndex = Math.max(0, t4List.size() - 11); - int endIndex = t4List.size() - 1; - + // 从最后17个开始(最小的线系数) + int startIndex = Math.max(0, t4List.size() - 17); List candidates = new ArrayList<>(); - for (int i = startIndex; i <= endIndex; i++) { + + for (int i = startIndex; i < t4List.size(); i++) { candidates.add(t4List.get(i).getSlaveBallNumber()); } - // 处理边界位置的相同线系数情况 - List result = handleT4BoundaryConflictsFor10(t4List, candidates, startIndex, endIndex); + // 处理第17个位置(系数最大的球号)的毛边情况 + if (candidates.size() == 17) { + int lastIndex = 16; // 第17个位置的索引 + Double lastCoefficient = t4List.get(lastIndex).getLineCoefficient(); + int actualLastIndex = lastIndex; + + // 查找第17个位置向上相同系数值的球号 + List candidatesFor17th = new ArrayList<>(); + + // 添加第17个位置本身 + candidatesFor17th.add(t4List.get(actualLastIndex)); + + // 向上查找相同系数值的球号 + for (int i = actualLastIndex - 1; i >= 0; i--) { + if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) { + candidatesFor17th.add(t4List.get(i)); + } else { + break; // 系数不同就停止查找 + } + } + + if (candidatesFor17th.size() > 1) { + log.debug("第17个位置及其向上有{}个相同线系数的候选:{}", candidatesFor17th.size(), + candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); + + Integer bestBall = selectBestBallFromHistoryTop100( + candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) + ); + + // 替换第17个位置的球号 + candidates.set(lastIndex, bestBall); + + log.debug("第17个位置毛边处理完成,最终选择:{}", bestBall); + } + } - log.debug("T4低位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); - return result; + log.debug("T4低位算法选择范围:[{}, {}],共{}个球", startIndex, t4List.size() - 1, candidates.size()); + return candidates.subList(0, Math.min(17, candidates.size())); } /** @@ -1350,12 +1528,21 @@ public class BallAnalysisService { List allNumbers = new ArrayList<>(); - // 第一步:处理第1个红球,从T7表获取10个数字 + // 第一步:处理第1个红球,从T7表获取10个数字(同时记录系数) log.info("第一步:处理第1个红球{},从T7表获取10个数字,使用{}级别算法", firstThreeRedBalls.get(0), level); List firstRedBallNumbers = getTop10FromT7(firstThreeRedBalls.get(0), level); allNumbers.addAll(firstRedBallNumbers); log.info("第1个红球{}获取到{}个数字:{}", firstThreeRedBalls.get(0), firstRedBallNumbers.size(), firstRedBallNumbers); + // 同步记录上述10个数字在T7表中的面系数,记录格式与analyzeBalls一致 + List step1BallsWithCoefficients = getT7CoefficientsFor(firstThreeRedBalls.get(0), firstRedBallNumbers); + log.info("=== 第一步(T7)球号和系数统计(共{}个) ===", step1BallsWithCoefficients.size()); + for (int i = 0; i < step1BallsWithCoefficients.size(); i++) { + BallWithCoefficient ballWithCoeff = step1BallsWithCoefficients.get(i); + log.info("第{}个:{}", i + 1, ballWithCoeff.toString()); + } + log.info("=== 第一步统计结束 ==="); + // 第二步:处理后6个红球,每个红球从T3表获取26个数字 log.info("第二步:处理后6个红球,每个红球从T3表获取26个数字"); for (int i = 0; i < lastSixRedBalls.size(); i++) { @@ -1388,8 +1575,8 @@ public class BallAnalysisService { log.info("总共收集到{}个数字", allNumbers.size()); - // 第六步:统计频率并获取前8个 - List result = getTop8ByFrequency(allNumbers); + // 第六步:统计频率并获取前8个(若同频超过8个,用第一步T7面系数作为二次筛选依据) + List result = getTop8ByFrequency(allNumbers, step1BallsWithCoefficients); log.info("跟随球号分析算法完成,结果:{}", result); return result; @@ -1936,7 +2123,7 @@ public class BallAnalysisService { * 统计数字出现频率,返回频率最高的前8个数字 * 如果频次相同的球号超过8个,使用T7表面系数进行二次筛选 */ - private List getTop8ByFrequency(List allNumbers) { + private List getTop8ByFrequency(List allNumbers, List step1BallsWithCoefficients) { log.debug("统计{}个数字的出现频率", allNumbers.size()); // 统计频率 @@ -1976,14 +2163,32 @@ public class BallAnalysisService { result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { - // 会超过8个,需要用T7表的面系数进行二次筛选 + // 会超过8个,需要用第一步记录的T7面系数进行二次筛选 int remainingSlots = 8 - result.size(); - log.info("需要从{}个频率相同的球号中选择{}个,使用T7表面系数进行筛选", balls.size(), remainingSlots); - - List selectedBalls = selectBallsByT7FaceCoefficient(balls, remainingSlots); + log.info("需要从{}个频率相同的球号中选择{}个,使用第一步T7面系数进行筛选(找不到则按0处理)", balls.size(), remainingSlots); + + // 构建 step1 球号->面系数 映射(找不到则为0) + Map num2Coeff = new HashMap<>(); + if (step1BallsWithCoefficients != null) { + for (BallWithCoefficient bwc : step1BallsWithCoefficients) { + num2Coeff.put(bwc.getBallNumber(), bwc.getCoefficient()); + } + } + + // 先按面系数降序,再按球号升序 + balls.sort((a, b) -> { + double ca = num2Coeff.getOrDefault(a, 0.0); + double cb = num2Coeff.getOrDefault(b, 0.0); + if (Double.compare(cb, ca) != 0) { + return Double.compare(cb, ca); + } + return Integer.compare(a, b); + }); + + List selectedBalls = balls.subList(0, Math.min(remainingSlots, balls.size())); result.addAll(selectedBalls); - log.info("通过T7表面系数筛选完成,最终选择:{}", selectedBalls); + log.info("通过第一步T7面系数筛选完成,最终选择:{}", selectedBalls); break; // 已经达到8个,结束 } @@ -2345,10 +2550,18 @@ public class BallAnalysisService { for (int i = 0; i < predictedRedBalls.size(); i++) { Integer redBall = predictedRedBalls.get(i); log.info("处理第{}个预测红球:{}", i + 1, redBall); - + List ballNumbers = getSimpleTop5FromT8ByLevel(redBall, level); allNumbers.addAll(ballNumbers); - + + // 额外记录这5个蓝球的面系数(与 fallowBallAnalysis 第一步风格一致) + List t8Coeffs = getT8CoefficientsFor(redBall, ballNumbers); + log.info("=== 预测红球{} 对应T8面系数(共{}个) ===", redBall, t8Coeffs.size()); + for (int k = 0; k < t8Coeffs.size(); k++) { + log.info("第{}个:{}", k + 1, t8Coeffs.get(k).toString()); + } + log.info("=== 记录结束 ==="); + log.info("预测红球{}从T8表获取到{}个蓝球号码:{}", redBall, ballNumbers.size(), ballNumbers); } @@ -2362,16 +2575,24 @@ public class BallAnalysisService { log.info("第四步:添加预测的2个蓝球号码:{}", predictedBlueBalls); allNumbers.addAll(predictedBlueBalls); - // 第五步:用上期蓝球从T4表获取26个蓝球号码 - log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", lastBlueBall); - List blueNumbers = getTop26FromT4(lastBlueBall); + // 第五步:用上期蓝球从T5表获取12个蓝球号码(含毛边处理) + log.info("第五步:用上期蓝球{}从T5表获取12个蓝球号码", lastBlueBall); + List blueNumbers = getTop12FromT5(lastBlueBall); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个蓝球号码", allNumbers.size()); - // 第六步:统计频率并获取前4个 - List result = getSimpleTop4ByFrequency(allNumbers); + // 汇总第二步收集的T8面系数,供第六步同频二次筛选使用 + List step2BlueT8Coefficients = new ArrayList<>(); + // 由于上面的循环内使用了临时变量 t8Coeffs,这里重新按预测红球聚合一次,确保完整 + for (Integer redBall : predictedRedBalls) { + List tmpBalls = getSimpleTop5FromT8ByLevel(redBall, level); + step2BlueT8Coefficients.addAll(getT8CoefficientsFor(redBall, tmpBalls)); + } + + // 第六步:统计频率并获取前4个(同频时使用第2步收集的T8系数求和作为第一级筛选) + List result = getSimpleTop4ByFrequency(allNumbers, step2BlueT8Coefficients); log.info("蓝球分析算法完成,结果:{}", result); return result; @@ -2558,28 +2779,167 @@ public class BallAnalysisService { List result = new ArrayList<>(); int size = t8List.size(); - + switch (level) { - case "H": - // 高位:取面系数最大的5个 - for (int i = 0; i < Math.min(5, size); i++) { + case "H": { + // 高位:系数最大值向下5个;第5个位置毛边:向下查找相同面系数,使用blue_history_top_100比较 + if (size < 5) { + log.warn("T8表数据不足5条,实际{}条", size); + return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()); + } + + // 前4个直接取 + for (int i = 0; i < 4; i++) { result.add(t8List.get(i).getSlaveBallNumber()); } - break; - case "M": - // 中位:取中间的5个 - int start = Math.max(0, (size - 5) / 2); - for (int i = start; i < Math.min(start + 5, size); i++) { - result.add(t8List.get(i).getSlaveBallNumber()); + + // 处理第5个位置的毛边 + Double fifthCoeff = t8List.get(4).getFaceCoefficient(); + List candidatesFor5th = new ArrayList<>(); + candidatesFor5th.add(t8List.get(4)); + for (int i = 5; i < size; i++) { + if (t8List.get(i).getFaceCoefficient().equals(fifthCoeff)) { + candidatesFor5th.add(t8List.get(i)); + } else { + break; + } + } + + if (candidatesFor5th.size() == 1) { + result.add(candidatesFor5th.get(0).getSlaveBallNumber()); + } else { + log.debug("高位第5位有{}个相同面系数的候选:{}", candidatesFor5th.size(), + candidatesFor5th.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())); + Integer bestBall = selectBestBallFromBlueHistoryTop100( + candidatesFor5th.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()) + ); + result.add(bestBall); } break; - case "L": - // 低位:取最后5个(面系数最小的) - int startLow = Math.max(0, size - 5); - for (int i = startLow; i < size; i++) { + } + case "M": { + // 中位:取比平均值大的、且最近的一个为中心,向上2个、向下2个,共5个; + // 上面两个的第一个(中心上方最近的)毛边向上查找;下面两个的第二个(更小的)毛边向下查找;均用blue_history_top_100比较 + if (size < 5) { + log.warn("T8表数据不足5条,实际{}条", size); + return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()); + } + + double avg = t8List.stream().mapToDouble(T8::getFaceCoefficient).average().orElse(0.0); + int avgIndex = -1; + double minDiff = Double.MAX_VALUE; + for (int i = 0; i < size; i++) { + double c = t8List.get(i).getFaceCoefficient(); + if (c > avg) { + double diff = c - avg; + if (diff <= minDiff) { // 取最后一个最接近的 + minDiff = diff; + avgIndex = i; + } + } + } + if (avgIndex == -1) { + avgIndex = 0; + } + + int startIndex = Math.max(0, avgIndex - 2); + int endIndex = Math.min(size - 1, avgIndex + 2); + while (endIndex - startIndex + 1 < 5 && (startIndex > 0 || endIndex < size - 1)) { + if (startIndex > 0) startIndex--; + if (endIndex < size - 1 && endIndex - startIndex + 1 < 5) endIndex++; + } + + for (int i = startIndex; i <= endIndex; i++) { result.add(t8List.get(i).getSlaveBallNumber()); } + + // 上面两个中的第一个(系数更大的那个):索引 startIndex(即 avgIndex-2) + int upFirstIndex = startIndex; + if (upFirstIndex >= 0) { + Double coeff = t8List.get(upFirstIndex).getFaceCoefficient(); + List candidatesUp = new ArrayList<>(); + // 包含窗口内的这个位置本身 + candidatesUp.add(t8List.get(upFirstIndex)); + // 只向上(窗口之外)查找相同面系数,避免与窗口内其它位置重复 + for (int i = upFirstIndex - 1; i >= 0; i--) { + if (t8List.get(i).getFaceCoefficient().equals(coeff)) { + candidatesUp.add(t8List.get(i)); + } else { + break; + } + } + if (candidatesUp.size() > 1) { + log.debug("中位上方第一个位置存在{}个相同面系数候选:{}", candidatesUp.size(), + candidatesUp.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())); + Integer best = selectBestBallFromBlueHistoryTop100( + candidatesUp.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()) + ); + // 替换窗口内的第一个位置 + result.set(0, best); + } + } + + // 下面两个中的第二个(系数更小的那个):索引 endIndex(即 avgIndex+2) + int downSecondIndex = endIndex; + if (downSecondIndex < size) { + Double coeff = t8List.get(downSecondIndex).getFaceCoefficient(); + List candidatesDown = new ArrayList<>(); + // 包含窗口内该位置 + candidatesDown.add(t8List.get(downSecondIndex)); + // 只向下(窗口之外)查找相同系数,避免与窗口内其它位置重复 + for (int i = downSecondIndex + 1; i < size; i++) { + if (t8List.get(i).getFaceCoefficient().equals(coeff)) { + candidatesDown.add(t8List.get(i)); + } else { + break; + } + } + if (candidatesDown.size() > 1) { + log.debug("中位下方第二个位置存在{}个相同面系数候选:{}", candidatesDown.size(), + candidatesDown.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())); + Integer best = selectBestBallFromBlueHistoryTop100( + candidatesDown.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()) + ); + // 替换窗口内的最后一个位置 + result.set(result.size() - 1, best); + } + } break; + } + case "L": { + // 低位:取系数倒数第二个向上5个;窗口顶部位置毛边:向上查找相同面系数,使用blue_history_top_100比较 + if (size < 5) { + log.warn("T8表数据不足5条,实际{}条", size); + return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()); + } + int endIndex = size - 2; // 倒数第二 + int startIndex = Math.max(0, endIndex - 4); + + for (int i = startIndex; i <= endIndex; i++) { + result.add(t8List.get(i).getSlaveBallNumber()); + } + + // 顶部位置(系数最大的)毛边处理:向上查找 + Double topCoeff = t8List.get(startIndex).getFaceCoefficient(); + List candidatesTop = new ArrayList<>(); + candidatesTop.add(t8List.get(startIndex)); + for (int i = startIndex - 1; i >= 0; i--) { + if (t8List.get(i).getFaceCoefficient().equals(topCoeff)) { + candidatesTop.add(t8List.get(i)); + } else { + break; + } + } + if (candidatesTop.size() > 1) { + log.debug("低位顶部位置存在{}个相同面系数候选:{}", candidatesTop.size(), + candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())); + Integer best = selectBestBallFromBlueHistoryTop100( + candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()) + ); + result.set(0, best); + } + break; + } } log.debug("T8表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result); @@ -2707,7 +3067,7 @@ public class BallAnalysisService { * 统计数字出现频率,返回频率最高的前4个蓝球号码 * 如果频次相同的球号超过4个,使用多级筛选机制 */ - private List getSimpleTop4ByFrequency(List allNumbers) { + private List getSimpleTop4ByFrequency(List allNumbers, List step2BlueT8Coefficients) { log.debug("统计{}个蓝球号码的出现频率", allNumbers.size()); // 统计频率 @@ -2747,14 +3107,35 @@ public class BallAnalysisService { result.addAll(balls); log.info("直接添加{}个蓝球号码,当前总数:{}", balls.size(), result.size()); } else { - // 会超过4个,需要用多级筛选机制 + // 会超过4个:第一级直接用“第二步收集的T8面系数求和”进行筛选 int remainingSlots = 4 - result.size(); - log.info("需要从{}个频率相同的蓝球号码中选择{}个,使用多级筛选机制", balls.size(), remainingSlots); - - List selectedBalls = selectBallsByMultiLevelFilteringForBlue4(balls, remainingSlots); + log.info("需要从{}个频率相同的蓝球号码中选择{}个,使用第二步T8面系数求和进行筛选", balls.size(), remainingSlots); + + // 统计同频候选在step2BlueT8Coefficients中的面系数和(找不到视为0) + Map sumMap = new HashMap<>(); + for (Integer b : balls) { + double sum = 0.0; + if (step2BlueT8Coefficients != null) { + for (BallWithCoefficient bwc : step2BlueT8Coefficients) { + if (b.equals(bwc.getBallNumber())) { + sum += bwc.getCoefficient(); + } + } + } + sumMap.put(b, sum); + } + + // 按“系数和降序、球号升序”排序,取前remainingSlots + balls.sort((a, b) -> { + int cmp = Double.compare(sumMap.getOrDefault(b, 0.0), sumMap.getOrDefault(a, 0.0)); + if (cmp != 0) return cmp; + return Integer.compare(a, b); + }); + + List selectedBalls = balls.subList(0, Math.min(remainingSlots, balls.size())); result.addAll(selectedBalls); - log.info("通过多级筛选完成,最终选择:{}", selectedBalls); + log.info("通过第二步T8面系数和筛选完成,最终选择:{}", selectedBalls); break; // 已经达到4个,结束 } @@ -3445,7 +3826,7 @@ public class BallAnalysisService { * @param masterBallNumber 主球号 */ private List getTop12FromT5(Integer masterBallNumber) { - log.debug("从T5表查询主球{}的线系数数据,取前12个", masterBallNumber); + log.debug("从T5表查询主球{}的线系数数据,取前12个(含毛边处理)", masterBallNumber); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); @@ -3465,37 +3846,119 @@ public class BallAnalysisService { return t5List.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList()); } - // 获取前11个 - List result = new ArrayList<>(); - for (int i = 0; i < 11; i++) { - result.add(t5List.get(i).getSlaveBallNumber()); + // 完全复刻补齐策略: + // 1) 找到第12位的阈值系数 targetCoefficient + // 2) 统计 strictlyGreaterCount = 面系数 > targetCoefficient 的数量 + // 3) 全部选入这 strictlyGreaterCount 个 + // 4) 在“等于 targetCoefficient”的集合中,按 blue_history_top_100 点系数选择 (12 - strictlyGreaterCount) 个 + // 5) 若等于集合不足,再从更小系数中按顺序补足 + + Double targetCoefficient = t5List.get(11).getLineCoefficient(); + + // 统计大于阈值的数量 + int strictlyGreaterCount = 0; + while (strictlyGreaterCount < t5List.size() && + t5List.get(strictlyGreaterCount).getLineCoefficient() > targetCoefficient) { + strictlyGreaterCount++; } - // 处理第12个位置:检查是否有相同的线系数 - Double targetCoefficient = t5List.get(11).getLineCoefficient(); - List candidatesFor12th = new ArrayList<>(); - - // 找出所有线系数等于第12个位置线系数的记录 - for (int i = 11; i < t5List.size(); i++) { - if (t5List.get(i).getLineCoefficient().equals(targetCoefficient)) { - candidatesFor12th.add(t5List.get(i)); + // 先加入所有大于阈值的球号 + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(strictlyGreaterCount, 12); i++) { + result.add(t5List.get(i).getSlaveBallNumber()); + } + if (result.size() >= 12) { + log.debug("T5表主球{}最终选择的{}个从球:{}", masterBallNumber, result.size(), result); + return result; + } + + // 收集等于阈值的连续区间(紧随其后的相等区) + List equalsGroup = new ArrayList<>(); + int idx = strictlyGreaterCount; + while (idx < t5List.size() && t5List.get(idx).getLineCoefficient().equals(targetCoefficient)) { + equalsGroup.add(t5List.get(idx)); + idx++; + } + + int needFromEquals = 12 - result.size(); + if (!equalsGroup.isEmpty()) { + List eqNums = equalsGroup.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList()); + if (eqNums.size() <= needFromEquals) { + result.addAll(eqNums); } else { - break; // 线系数不同,停止查找 + // 在等于组中先用 blue_history_top_100 分组排序,若边界存在同分,再到 blue_history_top 打破平局 + QueryWrapper qw = new QueryWrapper<>(); + qw.in("ballNumber", eqNums) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List top100 = blueHistoryTop100Mapper.selectList(qw); + + List picked = new ArrayList<>(); + if (top100.isEmpty()) { + log.warn("等值候选{}在blue_history_top_100中未找到,按球号升序取前{}个", eqNums, needFromEquals); + Collections.sort(eqNums); + picked.addAll(eqNums.subList(0, Math.min(needFromEquals, eqNums.size()))); + } else { + // 将top100结果按点系数分组(降序) + Map> pcGroups = new TreeMap<>(Collections.reverseOrder()); + Set inTop100 = new HashSet<>(); + for (BlueHistoryTop100 row : top100) { + inTop100.add(row.getBallNumber()); + pcGroups.computeIfAbsent(row.getPointCoefficient(), k -> new ArrayList<>()) + .add(row.getBallNumber()); + } + + // 按组累加,遇到超额的组(边界同分)用 blue_history_top 打破平局 + for (Map.Entry> e : pcGroups.entrySet()) { + if (picked.size() >= needFromEquals) break; + List group = e.getValue(); + int remaining = needFromEquals - picked.size(); + if (group.size() <= remaining) { + // 整组加入 + for (Integer n : group) { + if (!picked.contains(n)) picked.add(n); + } + } else { + // 边界同分:到 blue_history_top 进行最终排序 + QueryWrapper qwTop = new QueryWrapper<>(); + qwTop.in("ballNumber", group) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List topRows = blueHistoryTopMapper.selectList(qwTop); + if (!topRows.isEmpty()) { + List ordered = topRows.stream() + .map(BlueHistoryTop::getBallNumber) + .collect(Collectors.toList()); + picked.addAll(ordered.subList(0, Math.min(remaining, ordered.size()))); + } else { + // 若blue_history_top也没有,按球号升序取 + List ordered = new ArrayList<>(group); + Collections.sort(ordered); + picked.addAll(ordered.subList(0, Math.min(remaining, ordered.size()))); + } + break; + } + } + + // 若top100覆盖的候选仍不足needFromEquals,再从不在top100中的等值候选补齐(球号升序) + if (picked.size() < needFromEquals) { + List notInTop100 = eqNums.stream() + .filter(n -> !inTop100.contains(n)) + .sorted() + .collect(Collectors.toList()); + int remain = needFromEquals - picked.size(); + picked.addAll(notInTop100.subList(0, Math.min(remain, notInTop100.size()))); + } + } + result.addAll(picked); } } - if (candidatesFor12th.size() == 1) { - // 只有一个候选,直接添加 - result.add(candidatesFor12th.get(0).getSlaveBallNumber()); - } else { - // 有多个候选,通过blue_history_top_100表的点系数排序 - log.debug("第12位有{}个相同线系数的候选:{}", candidatesFor12th.size(), - candidatesFor12th.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList())); - - Integer bestBall = selectBestBallFromBlueHistoryTop100( - candidatesFor12th.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList()) - ); - result.add(bestBall); + // 若仍不足12,从更小系数的条目继续顺序补足 + idx = strictlyGreaterCount + equalsGroup.size(); + while (result.size() < 12 && idx < t5List.size()) { + result.add(t5List.get(idx).getSlaveBallNumber()); + idx++; } log.debug("T5表主球{}最终选择的{}个从球:{}", masterBallNumber, result.size(), result); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b6fd6e6..bba2d5c 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: root - password: root + username: cpzs_root + password: cpzs_123456 # 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: