diff --git a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java index 8cabc11..afa3ddc 100644 --- a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java +++ b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java @@ -419,7 +419,7 @@ public class BallAnalysisService { log.debug("第17个位置及其向下有{}个相同线系数的候选:{}", candidatesFor17th.size(), candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); @@ -521,7 +521,7 @@ public class BallAnalysisService { if (candidatesForFirst.size() > 1) { log.debug("第一位及其上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(0, bestBall); @@ -538,7 +538,7 @@ public class BallAnalysisService { if (candidatesForLast.size() > 1) { log.debug("最后一位及其下面有{}个相同线系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(result.size() - 1, bestBall); @@ -611,19 +611,19 @@ public class BallAnalysisService { // 处理边界毛边情况 if (result.size() >= 8) { - // 处理向上8个球号的第8个位置(索引为7)的毛边情况 - int upBoundaryIndex = 7; // 向上第8个球号的索引 + // 处理向上8个球号的第8个位置(对应区间起点 startIndex,即 result[0])的毛边情况 + int upBoundaryIndex = 0; // 向上第8个球号在 result 中的索引 if (upBoundaryIndex < result.size()) { Double upBoundaryCoefficient = result.get(upBoundaryIndex).getCoefficient(); // 查找向上还有没有相同系数值的球号 List upCandidates = new ArrayList<>(); - int actualUpIndex = startIndex + upBoundaryIndex; + int actualUpIndex = startIndex + upBoundaryIndex; // 即 startIndex // 添加边界球号本身 upCandidates.add(t3List.get(actualUpIndex)); - // 向上查找相同系数值的球号 + // 向上查找相同系数值的球号(在 startIndex 之上) for (int i = actualUpIndex - 1; i >= 0; i--) { if (t3List.get(i).getLineCoefficient().equals(upBoundaryCoefficient)) { upCandidates.add(t3List.get(i)); @@ -633,14 +633,14 @@ public class BallAnalysisService { } if (upCandidates.size() > 1) { - log.debug("向上第8个球号及其上面有{}个相同线系数的候选:{}", upCandidates.size(), + log.debug("向上第8个球号(startIndex)及其上面有{}个相同线系数的候选:{}", upCandidates.size(), upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); - // 找到对应的T3对象并替换第8个位置 + // 找到对应的T3对象并替换 result[0] T3 selectedT3 = upCandidates.stream() .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) .findFirst() @@ -651,19 +651,19 @@ public class BallAnalysisService { } } - // 处理向下8个球号的第8个位置的毛边情况 - int downBoundaryIndex = Math.min(16, result.size() - 1); // 向下第8个球号的索引(从中心点开始算) + // 处理向下8个球号的第8个位置(对应区间终点 endIndex,即 result[last])的毛边情况 + int downBoundaryIndex = Math.min(16, result.size() - 1); // 向下第8个球号在 result 中的索引 if (downBoundaryIndex >= 8 && downBoundaryIndex < result.size()) { Double downBoundaryCoefficient = result.get(downBoundaryIndex).getCoefficient(); // 查找向下还有没有相同系数值的球号 List downCandidates = new ArrayList<>(); - int actualDownIndex = startIndex + downBoundaryIndex; + int actualDownIndex = startIndex + downBoundaryIndex; // 即 endIndex // 添加边界球号本身 downCandidates.add(t3List.get(actualDownIndex)); - // 向下查找相同系数值的球号 + // 向下查找相同系数值的球号(在 endIndex 之下) for (int i = actualDownIndex + 1; i < t3List.size(); i++) { if (t3List.get(i).getLineCoefficient().equals(downBoundaryCoefficient)) { downCandidates.add(t3List.get(i)); @@ -673,14 +673,14 @@ public class BallAnalysisService { } if (downCandidates.size() > 1) { - log.debug("向下第8个球号及其下面有{}个相同线系数的候选:{}", downCandidates.size(), + log.debug("向下第8个球号(endIndex)及其下面有{}个相同线系数的候选:{}", downCandidates.size(), downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); - // 找到对应的T3对象并替换相应位置 + // 找到对应的T3对象并替换 result[last] T3 selectedT3 = downCandidates.stream() .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) .findFirst() @@ -726,11 +726,11 @@ public class BallAnalysisService { } if (candidatesForFirst.size() > 1) { - // 有多个候选,通过history_top_100表的点系数排序 + // 有多个候选,通过带回退的多级筛选 log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); @@ -765,7 +765,7 @@ public class BallAnalysisService { // 处理第17个位置(系数最大的球号)的毛边情况 if (candidates.size() == 17) { - int lastIndex = 16; // 第17个位置的索引 + int lastIndex = 0; // 第17个位置的索引 Double lastCoefficient = candidates.get(lastIndex).getCoefficient(); int actualLastIndex = startIndex + lastIndex; @@ -930,6 +930,9 @@ public class BallAnalysisService { Double firstCoefficient = t4List.get(startIndex).getLineCoefficient(); List candidatesForFirst = new ArrayList<>(); + // 先包含第一个位置本身 + candidatesForFirst.add(t4List.get(startIndex)); + // 找出第一个位置上面(如果存在)的所有相同线系数的记录 for (int i = 0; i < startIndex; i++) { if (t4List.get(i).getLineCoefficient().equals(firstCoefficient)) { @@ -937,12 +940,12 @@ public class BallAnalysisService { } } - if (candidatesForFirst.size() > 0) { - // 有多个候选,通过history_top_100表的点系数排序 - log.debug("第一位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), + if (candidatesForFirst.size() > 1) { + // 有多个候选,通过带回退的多级筛选 + log.debug("第一位上面及自身有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); @@ -954,6 +957,9 @@ public class BallAnalysisService { Double lastCoefficient = t4List.get(endIndex).getLineCoefficient(); List candidatesForLast = new ArrayList<>(); + // 先包含最后一个位置本身 + candidatesForLast.add(t4List.get(endIndex)); + // 找出最后一个位置下面(如果存在)的所有相同线系数的记录 for (int i = endIndex + 1; i < t4List.size(); i++) { if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) { @@ -961,12 +967,12 @@ public class BallAnalysisService { } } - if (candidatesForLast.size() > 0) { - // 有多个候选,通过history_top_100表的点系数排序 - log.debug("最后一位下面有{}个相同线系数的候选:{}", candidatesForLast.size(), + if (candidatesForLast.size() > 1) { + // 有多个候选,通过带回退的多级筛选 + log.debug("最后一位下面及自身有{}个相同线系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); @@ -997,7 +1003,7 @@ public class BallAnalysisService { // 处理第17个位置(系数最大的球号)的毛边情况 if (candidates.size() == 17) { - int lastIndex = 16; // 第17个位置的索引 + int lastIndex = 0; // 第17个位置的索引 Double lastCoefficient = t4List.get(lastIndex).getLineCoefficient(); int actualLastIndex = lastIndex; @@ -1020,7 +1026,7 @@ public class BallAnalysisService { log.debug("第17个位置及其向上有{}个相同线系数的候选:{}", candidatesFor17th.size(), candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); @@ -1058,7 +1064,7 @@ public class BallAnalysisService { log.debug("第1位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(0, bestBall); @@ -1079,7 +1085,7 @@ public class BallAnalysisService { log.debug("第10位下面有{}个相同线系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(result.size() - 1, bestBall); @@ -1126,7 +1132,7 @@ public class BallAnalysisService { log.debug("排行{}有{}个相同的球号:{}", current.getNo(), sameRankList.size(), sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList()) ); result.add(bestBall); @@ -1170,6 +1176,46 @@ public class BallAnalysisService { return bestBall; } + /** + * 在候选球号中进行多级回退选择: + * 1) 先用 history_top_100 的点系数最高者; + * 2) 若全部不在 history_top_100,则用 history_top 的点系数最高者; + * 3) 若仍无法区分,则随机选择一个。 + */ + private Integer selectBestBallWithFallback(List candidates) { + if (candidates == null || candidates.isEmpty()) { + throw new IllegalArgumentException("候选球号列表不能为空"); + } + + if (candidates.size() == 1) { + return candidates.get(0); + } + + // 优先:history_top_100 + QueryWrapper top100Query = new QueryWrapper<>(); + top100Query.in("ballNumber", candidates) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List top100List = historyTop100Mapper.selectList(top100Query); + if (top100List != null && !top100List.isEmpty()) { + return top100List.get(0).getBallNumber(); + } + + // 其次:history_top + QueryWrapper topQuery = new QueryWrapper<>(); + topQuery.in("ballNumber", candidates) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List topList = historyTopMapper.selectList(topQuery); + if (topList != null && !topList.isEmpty()) { + return topList.get(0).getBallNumber(); + } + + // 最后:随机 + Collections.shuffle(candidates); + return candidates.get(0); + } + /** * 统计数字出现频率,返回频率最高的前11个数字 * 如果频次相同的球号超过11个,使用多层筛选: @@ -1648,7 +1694,7 @@ public class BallAnalysisService { result = getHighLevelBallsFromT7(t7List); break; case "M": - // 中位:取面系数平均值向上6个向下4个的10个球号 + // 中位:取面系数平均值向上5个、向下4个,共10个球号 result = getMiddleLevelBallsFromT7(t7List); break; case "L": @@ -1708,7 +1754,11 @@ public class BallAnalysisService { } /** - * 中位算法:从T7表取面系数平均值向上5个向下5个的10个球号 + * 中位算法:从T7表取面系数平均值向上5个、向下4个,共10个球号。 + * 毛边处理要求: + * - 向上边界包含边界本身(或其上方/下方的同系数项)一起参与比较; + * - 向下边界包含边界本身(或其下方的同系数项)一起参与比较; + * - 当边界存在多个相同面系数的候选时,按 history_top_100 → history_top → 随机 的顺序筛选。 */ private List getMiddleLevelBallsFromT7(List t7List) { if (t7List.size() < 10) { @@ -1747,7 +1797,7 @@ public class BallAnalysisService { log.debug("找到大于平均值且最接近的位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient()); } - // 向上5个,向下5个,共10个 + // 向上5个,向下4个,共10个 int startIndex = Math.max(0, avgIndex - 5); int endIndex = Math.min(t7List.size() - 1, avgIndex + 4); @@ -1766,7 +1816,7 @@ public class BallAnalysisService { candidates.add(t7List.get(i).getSlaveBallNumber()); } - // 处理边界位置的相同面系数情况 + // 处理边界位置的相同面系数情况(包含边界本身参与比较,并带回退规则) List result = handleT7BoundaryConflicts(t7List, candidates, startIndex, endIndex); log.debug("T7中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); @@ -1782,9 +1832,9 @@ public class BallAnalysisService { return t7List.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()); } - // 从最后13个开始,取第3-13个(即倒数第11到倒数第1个) - int startIndex = Math.max(0, t7List.size() - 11); - int endIndex = t7List.size() - 1; + // 从最后13个开始,取第3-13个(即倒数第12到倒数第2个) + int startIndex = Math.max(0, t7List.size() - 12); + int endIndex = t7List.size() - 2; List candidates = new ArrayList<>(); for (int i = startIndex; i <= endIndex; i++) { @@ -1806,9 +1856,10 @@ public class BallAnalysisService { List result = new ArrayList<>(candidates); if (result.size() >= 10) { - // 检查第1个位置(最大面系数)的冲突 + // 检查第1个位置(最大面系数)的冲突:包含边界本身 Double firstCoefficient = t7List.get(startIndex).getFaceCoefficient(); List candidatesForFirst = new ArrayList<>(); + candidatesForFirst.add(t7List.get(startIndex)); // 边界本身 // 找出第一个位置上面(如果存在)的所有相同面系数的记录 for (int i = 0; i < startIndex; i++) { @@ -1817,19 +1868,20 @@ public class BallAnalysisService { } } - if (candidatesForFirst.size() > 0) { - log.debug("第1位上面有{}个相同面系数的候选:{}", candidatesForFirst.size(), + if (candidatesForFirst.size() > 1) { + log.debug("第1位及其上面有{}个相同面系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(0, bestBall); } - // 检查第10个位置(最小面系数)的冲突 + // 检查第10个位置(最小面系数)的冲突:包含边界本身 Double lastCoefficient = t7List.get(endIndex).getFaceCoefficient(); List candidatesForLast = new ArrayList<>(); + candidatesForLast.add(t7List.get(endIndex)); // 边界本身 // 找出第10个位置下面(如果存在)的所有相同面系数的记录 for (int i = endIndex + 1; i < t7List.size(); i++) { @@ -1838,11 +1890,11 @@ public class BallAnalysisService { } } - if (candidatesForLast.size() > 0) { - log.debug("第10位下面有{}个相同面系数的候选:{}", candidatesForLast.size(), + if (candidatesForLast.size() > 1) { + log.debug("第10位及其下面有{}个相同面系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); - Integer bestBall = selectBestBallFromHistoryTop100( + Integer bestBall = selectBestBallWithFallback( candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(result.size() - 1, bestBall); @@ -2163,9 +2215,9 @@ public class BallAnalysisService { result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { - // 会超过8个,需要用第一步记录的T7面系数进行二次筛选 + // 会超过8个:先按第一步T7面系数排序;如仍有边界相同面系数,继续按 100期 → 历史 → 随机 的回退规则,只在边界同系数的集合内决策 int remainingSlots = 8 - result.size(); - log.info("需要从{}个频率相同的球号中选择{}个,使用第一步T7面系数进行筛选(找不到则按0处理)", balls.size(), remainingSlots); + log.info("需要从{}个频率相同的球号中选择{}个,先按第一步T7面系数进行筛选(找不到则按0处理)", balls.size(), remainingSlots); // 构建 step1 球号->面系数 映射(找不到则为0) Map num2Coeff = new HashMap<>(); @@ -2176,7 +2228,8 @@ public class BallAnalysisService { } // 先按面系数降序,再按球号升序 - balls.sort((a, b) -> { + List sortedByT7 = new ArrayList<>(balls); + sortedByT7.sort((a, b) -> { double ca = num2Coeff.getOrDefault(a, 0.0); double cb = num2Coeff.getOrDefault(b, 0.0); if (Double.compare(cb, ca) != 0) { @@ -2185,11 +2238,40 @@ public class BallAnalysisService { return Integer.compare(a, b); }); - List selectedBalls = balls.subList(0, Math.min(remainingSlots, balls.size())); - result.addAll(selectedBalls); - - log.info("通过第一步T7面系数筛选完成,最终选择:{}", selectedBalls); - break; // 已经达到8个,结束 + // 如果正好能取齐,直接取 + if (sortedByT7.size() <= remainingSlots) { + result.addAll(sortedByT7); + log.info("按T7面系数后正好取齐,选择:{}", sortedByT7); + break; + } + + // 存在边界相同面系数:提取阈值、拆分高于阈值与阈值相等的集合 + double thresholdCoeff = num2Coeff.getOrDefault(sortedByT7.get(remainingSlots - 1), 0.0); + List higherThanThreshold = new ArrayList<>(); + List equalThresholdGroup = new ArrayList<>(); + for (Integer bn : sortedByT7) { + double c = num2Coeff.getOrDefault(bn, 0.0); + if (c > thresholdCoeff) { + higherThanThreshold.add(bn); + } else if (Double.compare(c, thresholdCoeff) == 0) { + equalThresholdGroup.add(bn); + } + } + + // 先无条件加入高于阈值的 + result.addAll(higherThanThreshold); + + int stillNeed = remainingSlots - higherThanThreshold.size(); + if (stillNeed > 0) { + // 仅在阈值相同的集合内,先用100期取该组的最高点系数组(可能少于需要的数量则直接全部加入), + // 若仍未满足,再对剩余候选使用history_top精筛;仍无法区分则随机补齐。 + List tieResolved = selectByTop100ThenHistoryTop(equalThresholdGroup, stillNeed); + result.addAll(tieResolved); + log.info("边界同面系数通过100期→历史→随机补齐:{}", tieResolved); + } + + // 已达到8个,结束 + break; } // 如果已经有8个,结束 @@ -2513,6 +2595,90 @@ public class BallAnalysisService { return result; } + /** + * 仅在一组同T7系数的并列候选中,按“先100期再历史最后随机”的规则补齐目标数量。 + * 行为符合“100期先拿能拿的直接加入;不足时再从剩余里用history_top补齐;仍不足再随机补齐”。 + */ + private List selectByTop100ThenHistoryTop(List candidateBalls, int needCount) { + if (candidateBalls == null || candidateBalls.isEmpty() || needCount <= 0) { + return new ArrayList<>(); + } + + // 在同T7系数的候选中,先用100期逐组加入;若某组加入会超出,则该组整体作为未决集合,交给下一层(history_top) + QueryWrapper query = new QueryWrapper<>(); + query.in("ballNumber", candidateBalls) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List top100List = historyTop100Mapper.selectList(query); + + List selectedByTop100 = new ArrayList<>(); + List undecidedGroup = new ArrayList<>(); + + if (top100List != null && !top100List.isEmpty()) { + // 分组(保持顺序) + Map> groups = new LinkedHashMap<>(); + for (HistoryTop100 item : top100List) { + groups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()) + .add(item.getBallNumber()); + } + for (Map.Entry> entry : groups.entrySet()) { + List groupBalls = entry.getValue(); + if (selectedByTop100.size() + groupBalls.size() <= needCount) { + selectedByTop100.addAll(groupBalls); + } else { + undecidedGroup.addAll(groupBalls); + break; // 只把“超出的那一组”交给下一层 + } + if (selectedByTop100.size() == needCount) { + break; + } + } + } + + if (selectedByTop100.size() >= needCount) { + return selectedByTop100.subList(0, needCount); + } + + // 还有缺口:仅对未决集合使用 history_top;若100期没有数据,则把所有候选作为未决集合 + int stillNeed = needCount - selectedByTop100.size(); + List forHistoryTop; + if (!undecidedGroup.isEmpty()) { + forHistoryTop = new ArrayList<>(undecidedGroup); + } else if (top100List == null || top100List.isEmpty()) { + // 100期没有任何记录,则全部交给历史表 + forHistoryTop = new ArrayList<>(candidateBalls); + } else { + // 100期记录不足但没有触发未决组(例如所有组总和仍小于needCount),则用剩余候选补齐 + forHistoryTop = new ArrayList<>(candidateBalls); + forHistoryTop.removeAll(selectedByTop100); + } + + List byTop = forHistoryTop.isEmpty() ? new ArrayList<>() + : selectBallsByHistoryTopPointCoefficient(forHistoryTop, stillNeed); + + List collected = new ArrayList<>(); + collected.addAll(selectedByTop100); + if (!byTop.isEmpty()) { + if (byTop.size() > stillNeed) { + byTop = byTop.subList(0, stillNeed); + } + collected.addAll(byTop); + } + + // 仍不足则在“未被选中的集合”中随机补齐 + int gap = needCount - collected.size(); + if (gap > 0) { + List left = new ArrayList<>(candidateBalls); + left.removeAll(collected); + if (!left.isEmpty()) { + List rnd = selectBallsRandomly(left, Math.min(gap, left.size())); + collected.addAll(rnd); + } + } + + return collected; + } + /** * 蓝球分析算法主方法 * @param level 高位/中位/低位标识 (H/M/L) @@ -2755,6 +2921,64 @@ public class BallAnalysisService { return bestBall; } + /** + * 蓝球多级回退选择:先用 blue_history_top_100,再用 blue_history_top,最后随机 + */ + private List selectBlueBallsWithFallback(List candidates, int needCount) { + if (candidates == null || candidates.isEmpty() || needCount <= 0) { + return new ArrayList<>(); + } + if (candidates.size() <= needCount) { + return new ArrayList<>(candidates); + } + + // 先用 blue_history_top_100 + QueryWrapper top100Query = new QueryWrapper<>(); + top100Query.in("ballNumber", candidates) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List top100List = blueHistoryTop100Mapper.selectList(top100Query); + + if (top100List != null && top100List.size() >= needCount) { + return top100List.subList(0, needCount).stream() + .map(BlueHistoryTop100::getBallNumber) + .collect(Collectors.toList()); + } + + // 不足,继续用 blue_history_top + List selectedBy100 = top100List == null ? new ArrayList<>() + : top100List.stream().map(BlueHistoryTop100::getBallNumber).collect(Collectors.toList()); + List remaining = new ArrayList<>(candidates); + remaining.removeAll(selectedBy100); + int stillNeed = needCount - selectedBy100.size(); + + QueryWrapper topQuery = new QueryWrapper<>(); + topQuery.in("ballNumber", remaining) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List topList = blueHistoryTopMapper.selectList(topQuery); + + List selectedByTop = topList == null ? new ArrayList<>() + : topList.stream().limit(stillNeed).map(BlueHistoryTop::getBallNumber).collect(Collectors.toList()); + + List result = new ArrayList<>(); + result.addAll(selectedBy100); + result.addAll(selectedByTop); + + // 仍不足则随机补齐 + int gap = needCount - result.size(); + if (gap > 0) { + List left = new ArrayList<>(candidates); + left.removeAll(result); + if (!left.isEmpty()) { + Collections.shuffle(left); + result.addAll(left.subList(0, Math.min(gap, left.size()))); + } + } + + return result; + } + /** * 从T8表获取指定红球对应的5个蓝球号码(简化版本) * @param masterBallNumber 主球号(红球) @@ -2907,7 +3131,7 @@ public class BallAnalysisService { break; } case "L": { - // 低位:取系数倒数第二个向上5个;窗口顶部位置毛边:向上查找相同面系数,使用blue_history_top_100比较 + // 低位:取系数倒数第二个向上5个;窗口顶部和底部位置均有毛边:使用blue_history_top_100/blue_history_top多级回退比较 if (size < 5) { log.warn("T8表数据不足5条,实际{}条", size); return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()); @@ -2915,11 +3139,13 @@ public class BallAnalysisService { int endIndex = size - 2; // 倒数第二 int startIndex = Math.max(0, endIndex - 4); + // 先按基本规则选出5个位置的球号 + List baseResult = new ArrayList<>(); for (int i = startIndex; i <= endIndex; i++) { - result.add(t8List.get(i).getSlaveBallNumber()); + baseResult.add(t8List.get(i).getSlaveBallNumber()); } - // 顶部位置(系数最大的)毛边处理:向上查找 + // 处理顶部位置(系数最大的)毛边:向上查找相同面系数 Double topCoeff = t8List.get(startIndex).getFaceCoefficient(); List candidatesTop = new ArrayList<>(); candidatesTop.add(t8List.get(startIndex)); @@ -2933,11 +3159,37 @@ public class BallAnalysisService { 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()) + List topSelected = selectBlueBallsWithFallback( + candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()), 1 ); - result.set(0, best); + if (!topSelected.isEmpty()) { + baseResult.set(0, topSelected.get(0)); + } } + + // 处理底部位置(系数最小的)毛边:向下查找相同面系数 + Double bottomCoeff = t8List.get(endIndex).getFaceCoefficient(); + List candidatesBottom = new ArrayList<>(); + candidatesBottom.add(t8List.get(endIndex)); + for (int i = endIndex + 1; i < size; i++) { + if (t8List.get(i).getFaceCoefficient().equals(bottomCoeff)) { + candidatesBottom.add(t8List.get(i)); + } else { + break; + } + } + if (candidatesBottom.size() > 1) { + log.debug("低位底部位置存在{}个相同面系数候选:{}", candidatesBottom.size(), + candidatesBottom.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())); + List bottomSelected = selectBlueBallsWithFallback( + candidatesBottom.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()), 1 + ); + if (!bottomSelected.isEmpty()) { + baseResult.set(baseResult.size() - 1, bottomSelected.get(0)); + } + } + + result = baseResult; break; } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bba2d5c..6e91acb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cpzs username: cpzs_root - password: cpzs_123456 + password: cpzs_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