package com.xy.xyaicpzs.service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.xy.xyaicpzs.domain.entity.*; import com.xy.xyaicpzs.mapper.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; import com.xy.xyaicpzs.domain.vo.BallHitRateVO; import com.xy.xyaicpzs.domain.vo.PrizeEstimateVO; import com.xy.xyaicpzs.domain.vo.RedBallHitRateVO; import com.xy.xyaicpzs.domain.vo.BallAnalysisResultVO; import com.xy.xyaicpzs.domain.vo.FollowBallAnalysisResultVO; import com.xy.xyaicpzs.domain.vo.BlueBallAnalysisResultVO; /** * 球号分析服务类 * 实现复杂的球号分析算法 */ @Slf4j @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; @Autowired private T4Mapper t4Mapper; @Autowired private HistoryTopMapper historyTopMapper; @Autowired private HistoryTop100Mapper historyTop100Mapper; @Autowired private T7Mapper t7Mapper; @Autowired private T5Mapper t5Mapper; @Autowired private T6Mapper t6Mapper; @Autowired private T8Mapper t8Mapper; @Autowired private BlueHistoryTop100Mapper blueHistoryTop100Mapper; @Autowired private BlueHistoryTopMapper blueHistoryTopMapper; @Autowired private PredictRecordMapper predictRecordMapper; @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) * @param redBalls 6个红球号码 * @param blueBall 1个蓝球号码 * @return 分析结果:出现频率最高的前11位数字 */ public List analyzeBalls(String level, List redBalls, Integer blueBall) { log.info("开始球号分析算法,级别:{},红球:{},蓝球:{}", level, redBalls, blueBall); // 验证输入参数 if (redBalls == null || redBalls.size() != 6) { throw new IllegalArgumentException("红球数量必须为6个"); } if (blueBall == null) { throw new IllegalArgumentException("蓝球不能为空"); } if (!Arrays.asList("H", "M", "L").contains(level)) { throw new IllegalArgumentException("级别必须为H、M或L"); } List allNumbers = new ArrayList<>(); // 第一步:记录球号和系数的对应关系 List ballsWithCoefficients = new ArrayList<>(); // 第一步:处理6个红球,每个红球获取17个数字 log.info("第一步:处理6个红球,使用{}级别算法", level); for (int i = 0; i < redBalls.size(); i++) { Integer redBall = redBalls.get(i); log.info("处理第{}个红球:{}", i + 1, redBall); 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个球号"); List top3Numbers = getTop3FromHistoryTop(); allNumbers.addAll(top3Numbers); log.info("从history_top获取到{}个数字:{}", top3Numbers.size(), top3Numbers); // 第三步:用蓝球从t4表获取17个数字 log.info("第三步:用蓝球{}从t4表获取17个数字,使用{}级别算法", blueBall, level); List blueNumbers = getTop17FromT4(blueBall, level); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个数字", allNumbers.size()); // 第四步:统计频率并获取前11个 List result = getTop11ByFrequency(allNumbers, ballsWithCoefficients); log.info("球号分析算法完成,结果:{}", result); return result; } /** * 球号分析算法(包含筛选过程说明) * @param level 高位/中位/低位标识 (H/M/L) * @param redBalls 6个红球号码 * @param blueBall 蓝球号码 * @return 分析结果:出现频率最高的前11位数字及筛选过程说明 */ public BallAnalysisResultVO analyzeBallsWithProcess(String level, List redBalls, Integer blueBall) { log.info("开始球号分析算法(含过程说明),级别:{},红球:{},蓝球:{}", level, redBalls, blueBall); // 验证输入参数 if (redBalls == null || redBalls.size() != 6) { throw new IllegalArgumentException("红球数量必须为6个"); } if (blueBall == null) { throw new IllegalArgumentException("蓝球不能为空"); } if (!Arrays.asList("H", "M", "L").contains(level)) { throw new IllegalArgumentException("级别必须为H、M或L"); } List allNumbers = new ArrayList<>(); // 第一步:记录球号和系数的对应关系 List ballsWithCoefficients = new ArrayList<>(); // 第一步:处理6个红球,每个红球获取17个数字 log.info("第一步:处理6个红球,使用{}级别算法", level); for (int i = 0; i < redBalls.size(); i++) { Integer redBall = redBalls.get(i); log.info("处理第{}个红球:{}", i + 1, redBall); 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个球号"); List top3Numbers = getTop3FromHistoryTop(); allNumbers.addAll(top3Numbers); log.info("从history_top获取到{}个数字:{}", top3Numbers.size(), top3Numbers); // 第三步:用蓝球从t4表获取17个数字 log.info("第三步:用蓝球{}从t4表获取17个数字,使用{}级别算法", blueBall, level); List blueNumbers = getTop17FromT4(blueBall, level); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个数字", allNumbers.size()); // 第四步:统计频率并获取前11个(包含过程说明) BallAnalysisResultVO result = getTop11ByFrequencyWithProcess(allNumbers, ballsWithCoefficients); log.info("球号分析算法完成,结果:{}", result.getResult()); 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个从球号 * 根据不同级别使用不同的选择策略 * @param masterBallNumber 主球号 * @param level 级别:H-高位,M-中位,L-低位 */ private List getTop17FromT3(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 = getHighLevelBalls(t3List); break; case "M": // 中位:取线系数平均值向上9个球(含),向下8个球,共17个 result = getMiddleLevelBalls(t3List); break; case "L": // 低位:取最小值向上17个球,含最小值 result = getLowLevelBalls(t3List); break; } log.debug("T3表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result); return result; } /** * 从T4表获取指定主球的17个从球号 * 根据不同级别使用不同的选择策略 * @param masterBallNumber 主球号 * @param level 级别:H-高位,M-中位,L-低位 */ private List getTop17FromT4(Integer masterBallNumber, String level) { log.debug("从T4表查询主球{}的线系数数据,级别:{}", masterBallNumber, level); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("lineCoefficient") .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 List t4List = t4Mapper.selectList(queryWrapper); if (t4List.isEmpty()) { log.warn("T4表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } List result = new ArrayList<>(); switch (level) { case "H": // 高位:取前17个(按线系数从大到小排列) result = getHighLevelBallsFromT4(t4List); break; case "M": // 中位:取线系数平均值向上9个球(含),向下8个球,共17个 result = getMiddleLevelBallsFromT4(t4List); break; case "L": // 低位:取最小值向上17个球,含最小值 result = getLowLevelBallsFromT4(t4List); break; } log.debug("T4表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result); return result; } /** * 高位算法:从T3表取前17个(按线系数从大到小排列) */ private List getHighLevelBalls(List t3List) { if (t3List.size() < 17) { log.warn("T3表数据不足17条,实际{}条", t3List.size()); return t3List.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前16个 List result = new ArrayList<>(); for (int i = 0; i < 16; i++) { result.add(t3List.get(i).getSlaveBallNumber()); } // 处理第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) { // 只有一个候选,直接添加 result.add(candidatesFor17th.get(0).getSlaveBallNumber()); } 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()) ); result.add(bestBall); } return result; } /** * 高位算法:从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()); } // 获取前17个 List result = new ArrayList<>(); 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个位置(系数最小的球号)的毛边情况 if (result.size() == 17) { int lastIndex = 16; // 第17个位置的索引 Double lastCoefficient = result.get(lastIndex).getCoefficient(); // 查找第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 = selectBestBallWithFallback( 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; } /** * 中位算法:从T3表取线系数平均值向上8个球,向下8个球,共17个 */ private List getMiddleLevelBalls(List t3List) { if (t3List.size() < 17) { log.warn("T3表数据不足17条,实际{}条", t3List.size()); return t3List.stream().map(T3::getSlaveBallNumber).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++) { 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 = selectBestBallWithFallback( 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 = selectBestBallWithFallback( 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)); } // 处理边界毛边情况 if (result.size() >= 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; // 即 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)); } else { break; // 系数不同就停止查找 } } if (upCandidates.size() > 1) { log.debug("向上第8个球号(startIndex)及其上面有{}个相同线系数的候选:{}", upCandidates.size(), upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); // 找到对应的T3对象并替换 result[0] 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个位置(对应区间终点 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; // 即 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)); } else { break; // 系数不同就停止查找 } } if (downCandidates.size() > 1) { log.debug("向下第8个球号(endIndex)及其下面有{}个相同线系数的候选:{}", downCandidates.size(), downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); // 找到对应的T3对象并替换 result[last] 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; } /** * 低位算法:从T3表取最小值向上17个球,含最小值 */ private List getLowLevelBalls(List t3List) { if (t3List.size() < 17) { log.warn("T3表数据不足17条,实际{}条", t3List.size()); return t3List.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()); } // 从最后17个开始(最小的线系数) int startIndex = Math.max(0, t3List.size() - 17); List candidates = new ArrayList<>(); for (int i = startIndex; i < t3List.size(); i++) { candidates.add(t3List.get(i).getSlaveBallNumber()); } // 处理第一个位置(最大线系数)的相同值情况 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) { // 有多个候选,通过带回退的多级筛选 log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); // 替换第一个位置 candidates.set(0, bestBall); } } log.debug("低位算法选择范围:[{}, {}],共{}个球", startIndex, t3List.size() - 1, candidates.size()); 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)); } // 处理第17个位置(系数最大的球号)的毛边情况 if (candidates.size() == 17) { int lastIndex = 0; // 第17个位置的索引 Double lastCoefficient = candidates.get(lastIndex).getCoefficient(); int actualLastIndex = startIndex + lastIndex; // 查找第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 (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)); candidates.set(lastIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber)); log.debug("第17个位置毛边处理完成,最终选择:{}", bestBall); } } log.debug("低位算法选择范围:[{}, {}],共{}个球", startIndex, t3List.size() - 1, candidates.size()); return candidates.subList(0, Math.min(17, candidates.size())); } /** * 高位算法:从T4表取前17个(按线系数从大到小排列) */ private List getHighLevelBallsFromT4(List t4List) { if (t4List.size() < 17) { log.warn("T4表数据不足17条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前16个 List result = new ArrayList<>(); for (int i = 0; i < 16; i++) { result.add(t4List.get(i).getSlaveBallNumber()); } // 处理第17个位置:检查是否有相同的线系数 Double targetCoefficient = t4List.get(16).getLineCoefficient(); List candidatesFor17th = new ArrayList<>(); // 找出所有线系数等于第17个位置线系数的记录 for (int i = 16; i < t4List.size(); i++) { if (t4List.get(i).getLineCoefficient().equals(targetCoefficient)) { candidatesFor17th.add(t4List.get(i)); } else { break; // 线系数不同,停止查找 } } if (candidatesFor17th.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor17th.get(0).getSlaveBallNumber()); } else { // 有多个候选,通过history_top_100表的点系数排序 log.debug("第17位有{}个相同线系数的候选:{}", candidatesFor17th.size(), candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } return result; } /** * 中位算法:从T4表取线系数平均值向上8个球,向下8个球,共17个 */ private List getMiddleLevelBallsFromT4(List t4List) { if (t4List.size() < 17) { log.warn("T4表数据不足17条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } // 计算线系数平均值 double avgCoefficient = t4List.stream() .mapToDouble(T4::getLineCoefficient) .average() .orElse(0.0); log.debug("T4表线系数平均值:{}", avgCoefficient); // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) int avgIndex = -1; double minDiff = Double.MAX_VALUE; for (int i = 0; i < t4List.size(); i++) { double coefficient = t4List.get(i).getLineCoefficient(); // 只考虑大于平均值的值 if (coefficient > avgCoefficient) { double diff = coefficient - avgCoefficient; if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 minDiff = diff; avgIndex = i; } } } // 如果没有找到大于平均值的,则取最大值 if (avgIndex == -1) { avgIndex = 0; // T4表按线系数降序排列,第一个就是最大值 log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); } else { log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); } // 向上8个,向下8个,共17个 int startIndex = Math.max(0, avgIndex - 8); int endIndex = Math.min(t4List.size() - 1, avgIndex + 8); // 确保总共17个数字 while (endIndex - startIndex + 1 < 17 && (startIndex > 0 || endIndex < t4List.size() - 1)) { if (startIndex > 0) { startIndex--; } if (endIndex < t4List.size() - 1 && endIndex - startIndex + 1 < 17) { endIndex++; } } List result = new ArrayList<>(); for (int i = startIndex; i <= endIndex && result.size() < 17; i++) { 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<>(); // 先包含第一个位置本身 candidatesForFirst.add(t4List.get(startIndex)); // 找出第一个位置上面(如果存在)的所有相同线系数的记录 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("第一位上面及自身有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); // 替换第一个位置 result.set(0, bestBall); } // 检查最后一个位置(最小线系数)的冲突 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)) { candidatesForLast.add(t4List.get(i)); } } if (candidatesForLast.size() > 1) { // 有多个候选,通过带回退的多级筛选 log.debug("最后一位下面及自身有{}个相同线系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); // 替换最后一个位置 result.set(result.size() - 1, bestBall); } } return result; } /** * 低位算法:从T4表取最小值向上17个球,含最小值,共17个球号 */ private List getLowLevelBallsFromT4(List t4List) { if (t4List.size() < 17) { log.warn("T4表数据不足17条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } // 从最后17个开始(最小的线系数) int startIndex = Math.max(0, t4List.size() - 17); List candidates = new ArrayList<>(); for (int i = startIndex; i < t4List.size(); i++) { candidates.add(t4List.get(i).getSlaveBallNumber()); } // 处理第17个位置(系数最大的球号)的毛边情况 if (candidates.size() == 17) { int lastIndex = 0; // 第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 = selectBestBallWithFallback( candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); // 替换第17个位置的球号 candidates.set(lastIndex, bestBall); log.debug("第17个位置毛边处理完成,最终选择:{}", bestBall); } } log.debug("T4低位算法选择范围:[{}, {}],共{}个球", startIndex, t4List.size() - 1, candidates.size()); return candidates.subList(0, Math.min(17, candidates.size())); } /** * 处理T4表边界位置的线系数冲突(10个球版本) */ private List handleT4BoundaryConflictsFor10(List t4List, List candidates, int startIndex, int endIndex) { List result = new ArrayList<>(candidates); if (result.size() >= 10) { // 检查第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) { log.debug("第1位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(0, bestBall); } // 检查第10个位置(最小线系数)的冲突 Double lastCoefficient = t4List.get(endIndex).getLineCoefficient(); List candidatesForLast = new ArrayList<>(); // 找出第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() > 0) { log.debug("第10位下面有{}个相同线系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(result.size() - 1, bestBall); } } return result.subList(0, Math.min(10, result.size())); } /** * 从history_top表获取前3个球号(按点系数排行) * 如果点系数相同,通过history_top_100表比较 */ private List getTop3FromHistoryTop() { log.debug("从history_top表获取前3个球号"); // 查询前3个排行的数据 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.orderByAsc("no").last("LIMIT 3"); List topList = historyTopMapper.selectList(queryWrapper); if (topList.size() < 3) { log.warn("history_top表数据不足3条,实际{}条", topList.size()); return topList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList()); } List result = new ArrayList<>(); // 处理每个位置,检查是否有相同点系数的情况 for (int i = 0; i < 3; i++) { HistoryTop current = topList.get(i); // 查找所有具有相同排行的记录 QueryWrapper sameRankQuery = new QueryWrapper<>(); sameRankQuery.eq("no", current.getNo()); List sameRankList = historyTopMapper.selectList(sameRankQuery); if (sameRankList.size() == 1) { // 只有一个,直接添加 result.add(current.getBallNumber()); } else { // 有多个相同排行,通过history_top_100表比较点系数 log.debug("排行{}有{}个相同的球号:{}", current.getNo(), sameRankList.size(), sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } } log.debug("history_top表最终选择的3个球号:{}", result); return result; } /** * 从候选球号中选择history_top_100表中点系数最大的球号 */ private Integer selectBestBallFromHistoryTop100(List candidates) { if (candidates.isEmpty()) { throw new IllegalArgumentException("候选球号列表不能为空"); } if (candidates.size() == 1) { return candidates.get(0); } log.debug("从history_top_100表中比较候选球号:{}", candidates); // 查询这些球号在history_top_100表中的点系数 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.in("ballNumber", candidates) .orderByDesc("pointCoefficient") .orderByAsc("ballNumber"); // 点系数相同时按球号升序 List top100List = historyTop100Mapper.selectList(queryWrapper); if (top100List.isEmpty()) { log.warn("候选球号{}在history_top_100表中未找到数据,返回第一个", candidates); return candidates.get(0); } Integer bestBall = top100List.get(0).getBallNumber(); log.debug("从候选{}中选择最佳球号:{}(点系数:{})", candidates, bestBall, top100List.get(0).getPointCoefficient()); 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个,使用多层筛选: * 1. ballNumbersWithCoefficients系数和筛选 * 2. history_top_100表排名筛选 * 3. history_top表点系数筛选 * 4. 随机选择 */ private List getTop11ByFrequency(List allNumbers, List ballsWithCoefficients) { log.debug("统计{}个数字的出现频率", allNumbers.size()); // 统计频率 Map frequencyMap = new HashMap<>(); for (Integer number : allNumbers) { frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); } 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()) { Integer frequency = entry.getValue(); Integer ballNumber = entry.getKey(); frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); } log.debug("按频率分组:{}", frequencyGroups); List result = new ArrayList<>(); // 按频率从高到低处理 for (Map.Entry> group : frequencyGroups.entrySet()) { Integer frequency = group.getKey(); List balls = group.getValue(); // 对同频率的球号按数字升序排序 Collections.sort(balls); log.info("频率{}的球号:{}", frequency, balls); // 检查加入这组球号后是否会超过11个 if (result.size() + balls.size() <= 11) { // 不会超过11个,直接添加所有球号 result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { // 会超过11个,需要从这组球号中选择部分 int remainingSlots = 11 - result.size(); log.info("需要从{}个频率相同的球号中选择{}个,开始多层筛选", balls.size(), remainingSlots); List selectedBalls = selectBallsByMultiLevelFiltering(balls, remainingSlots, ballsWithCoefficients); result.addAll(selectedBalls); log.info("多层筛选完成,最终选择:{}", selectedBalls); break; // 已经达到11个,结束 } // 如果已经有11个,结束 if (result.size() >= 11) { break; } } log.info("频率统计最终结果(共{}个):{}", result.size(), result); // 打印详细的频率信息 for (int i = 0; i < result.size(); i++) { Integer ballNumber = result.get(i); Integer frequency = frequencyMap.get(ballNumber); log.info("第{}位:数字{},出现{}次", i + 1, ballNumber, frequency); } return result; } /** * 统计数字出现频率,返回频率最高的前11个数字(包含筛选过程说明) * 如果频次相同的球号超过11个,使用多层筛选: * 1. ballNumbersWithCoefficients系数和筛选 * 2. history_top_100表排名筛选 * 3. history_top表点系数筛选 * 4. 随机选择 */ private BallAnalysisResultVO getTop11ByFrequencyWithProcess(List allNumbers, List ballsWithCoefficients) { log.debug("统计{}个数字的出现频率", allNumbers.size()); StringBuilder processDescription = new StringBuilder(); // 统计频率 Map frequencyMap = new HashMap<>(); for (Integer number : allNumbers) { frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); } log.debug("数字频率统计:{}", frequencyMap); // 按频率分组 Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : frequencyMap.entrySet()) { Integer frequency = entry.getValue(); Integer ballNumber = entry.getKey(); frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); } List result = new ArrayList<>(); boolean needMultiLevelFiltering = false; List multiLevelCandidates = new ArrayList<>(); List directlySelectedBalls = new ArrayList<>(); List secondaryFilteredBalls = new ArrayList<>(); // 统计所有参与筛选的高频球号(用于显示) Map> allCandidateFrequencyGroups = new LinkedHashMap<>(); int highestFrequency = frequencyGroups.keySet().iterator().next(); // 收集所有可能参与筛选的球号(不只是最高频率,而是前几个高频率组) int frequencyThreshold = Math.max(1, highestFrequency - 2); // 显示最高频率、次高频率、第三高频率等 for (Map.Entry> group : frequencyGroups.entrySet()) { Integer frequency = group.getKey(); List balls = group.getValue(); Collections.sort(balls); // 只收集较高频率的球号用于显示 if (frequency >= frequencyThreshold) { allCandidateFrequencyGroups.put(frequency, new ArrayList<>(balls)); } } // 按频率从高到低处理,进行实际筛选 for (Map.Entry> group : frequencyGroups.entrySet()) { List balls = group.getValue(); Collections.sort(balls); // 检查加入这组球号后是否会超过11个 if (result.size() + balls.size() <= 11) { // 不会超过11个,直接添加所有球号 result.addAll(balls); directlySelectedBalls.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { // 会超过11个,需要从这组球号中选择部分 int remainingSlots = 11 - result.size(); log.info("需要从{}个频率相同的球号中选择{}个,开始多层筛选", balls.size(), remainingSlots); needMultiLevelFiltering = true; multiLevelCandidates = new ArrayList<>(balls); // 使用新的筛选方法,返回详细的筛选步骤信息 MultiLevelFilteringResult filteringResult = performMultiLevelFilteringWithDetails(balls, remainingSlots, ballsWithCoefficients); result.addAll(filteringResult.getSelectedBalls()); secondaryFilteredBalls = new ArrayList<>(filteringResult.getSelectedBalls()); // 记录筛选步骤信息 processDescription.append("筛选步骤:").append(filteringResult.getStepsDescription()); if (!filteringResult.getDetailedStepsInfo().isEmpty()) { processDescription.append(" ").append(filteringResult.getDetailedStepsInfo()); } log.info("多层筛选完成,最终选择:{}", filteringResult.getSelectedBalls()); break; // 已经达到11个,结束 } // 如果已经有11个,结束 if (result.size() >= 11) { break; } } // 构建详细的筛选过程说明 StringBuilder detailedDescription = new StringBuilder(); if (allCandidateFrequencyGroups.isEmpty()) { detailedDescription.append("所有球号出现频率相同,"); } else { detailedDescription.append("参与筛选的候选球号按频率分布为") .append(formatBallNumbersWithFrequency(allCandidateFrequencyGroups)) .append(";"); } if (!needMultiLevelFiltering) { detailedDescription.append("直接按频率筛选出前11个球号:") .append(formatBallNumbersComplete(result)) .append("。筛选步骤:通过频率筛选确定所有球号,无需进行T3系数和筛选、百期排位、历史排位。"); } else { detailedDescription.append("无法直接筛选出前11个"); if (!directlySelectedBalls.isEmpty()) { detailedDescription.append(",其中") .append(formatBallNumbers(directlySelectedBalls)) .append("直接入选"); } detailedDescription.append(",") .append(formatBallNumbers(multiLevelCandidates)) .append("需要进行二次筛选,最终筛选出") .append(formatBallNumbers(secondaryFilteredBalls)) .append(",组成前11个球号:") .append(formatBallNumbersComplete(result)) .append("。") .append(processDescription.toString()); } log.info("频率统计最终结果(共{}个):{}", result.size(), result); return BallAnalysisResultVO.builder() .result(result) .filteringProcess(detailedDescription.toString()) .build(); } /** * 多层筛选:ballNumbersWithCoefficients系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择 */ private List selectBallsByMultiLevelFiltering(List candidateBalls, int selectCount, List ballsWithCoefficients) { log.debug("开始多层筛选,候选球号:{},需要选择:{}个", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { return new ArrayList<>(candidateBalls); } List currentCandidates = new ArrayList<>(candidateBalls); // 第一层:ballNumbersWithCoefficients系数和筛选 log.debug("=== 第一层:ballNumbersWithCoefficients系数和筛选 ==="); List ballNumbersFiltered = selectBallsByBallNumbersWithCoefficients(currentCandidates, selectCount, ballsWithCoefficients); log.debug("ballNumbersWithCoefficients系数和筛选结果:{}", ballNumbersFiltered); // 如果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系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择 */ private List selectBallsByMultiLevelFilteringWithProcess(List candidateBalls, int selectCount, List ballsWithCoefficients, StringBuilder processDescription) { log.debug("开始多层筛选(含过程说明),候选球号:{},需要选择:{}个", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { return new ArrayList<>(candidateBalls); } List currentCandidates = new ArrayList<>(candidateBalls); List filteringSteps = new ArrayList<>(); // 第一层:ballNumbersWithCoefficients系数和筛选 log.debug("=== 第一层:ballNumbersWithCoefficients系数和筛选 ==="); List ballNumbersFiltered = selectBallsByBallNumbersWithCoefficients(currentCandidates, selectCount, ballsWithCoefficients); log.debug("ballNumbersWithCoefficients系数和筛选结果:{}", ballNumbersFiltered); if (ballNumbersFiltered.size() < currentCandidates.size()) { filteringSteps.add("T3系数和筛选"); } // 如果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() < currentCandidates.size()) { filteringSteps.add("history_top_100表排名筛选"); } 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() < currentCandidates.size()) { filteringSteps.add("history_top表点系数筛选"); } 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); if (currentCandidates.size() > selectCount) { filteringSteps.add("随机选择"); } return randomSelected; } /** * 筛选详情结果内部类 */ private static class FilteringDetailResult { private List filteredBalls; private String detailInfo; public FilteringDetailResult(List filteredBalls, String detailInfo) { this.filteredBalls = filteredBalls; this.detailInfo = detailInfo; } public List getFilteredBalls() { return filteredBalls; } public String getDetailInfo() { return detailInfo; } } /** * 多层筛选结果内部类 */ private static class MultiLevelFilteringResult { private List selectedBalls; private String stepsDescription; private String detailedStepsInfo; public MultiLevelFilteringResult(List selectedBalls, String stepsDescription) { this.selectedBalls = selectedBalls; this.stepsDescription = stepsDescription; this.detailedStepsInfo = ""; } public MultiLevelFilteringResult(List selectedBalls, String stepsDescription, String detailedStepsInfo) { this.selectedBalls = selectedBalls; this.stepsDescription = stepsDescription; this.detailedStepsInfo = detailedStepsInfo; } public List getSelectedBalls() { return selectedBalls; } public String getStepsDescription() { return stepsDescription; } public String getDetailedStepsInfo() { return detailedStepsInfo; } } /** * 跟随球号多级筛选结果内部类 */ private static class FollowBallMultiLevelFilteringResult { private List selectedBalls; private String stepsDescription; private String detailedStepsInfo; public FollowBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription) { this.selectedBalls = selectedBalls; this.stepsDescription = stepsDescription; this.detailedStepsInfo = ""; } public FollowBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription, String detailedStepsInfo) { this.selectedBalls = selectedBalls; this.stepsDescription = stepsDescription; this.detailedStepsInfo = detailedStepsInfo; } public List getSelectedBalls() { return selectedBalls; } public String getStepsDescription() { return stepsDescription; } public String getDetailedStepsInfo() { return detailedStepsInfo; } } /** * 跟随球号多级筛选(带详细过程说明) * @param candidateBalls 候选球号 * @param selectCount 需要选择的数量 * @param step1BallsWithCoefficients 第一步T7面系数数据 * @return 筛选结果和详细说明 */ private FollowBallMultiLevelFilteringResult performFollowBallMultiLevelFilteringWithDetails(List candidateBalls, int selectCount, List step1BallsWithCoefficients) { List completedSteps = new ArrayList<>(); List skippedSteps = new ArrayList<>(); // 构建 step1 球号->面系数 映射(找不到则为0) Map num2Coeff = new HashMap<>(); if (step1BallsWithCoefficients != null) { for (BallWithCoefficient bwc : step1BallsWithCoefficients) { num2Coeff.put(bwc.getBallNumber(), bwc.getCoefficient()); } } // 先按面系数降序,再按球号升序 List sortedByT7 = new ArrayList<>(candidateBalls); sortedByT7.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); }); // 记录所有候选球号的T7面系数详情 StringBuilder t7Details = new StringBuilder(); Map> t7Groups = new TreeMap<>(Collections.reverseOrder()); for (Integer ball : candidateBalls) { double coeff = num2Coeff.getOrDefault(ball, 0.0); t7Groups.computeIfAbsent(coeff, k -> new ArrayList<>()).add(ball); } for (Map.Entry> entry : t7Groups.entrySet()) { List balls = entry.getValue(); Collections.sort(balls); if (t7Details.length() > 0) t7Details.append(","); t7Details.append(formatBallNumbers(balls)).append("(T7面系数").append(String.format("%.2f", entry.getKey())).append(")"); } // 如果正好能取齐,直接取 if (sortedByT7.size() <= selectCount) { completedSteps.add("通过T7面系数筛选确定"); skippedSteps.add("百期排位"); skippedSteps.add("历史排位"); skippedSteps.add("随机选择"); String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); String detailInfo = "T7面系数筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + formatBallNumbers(sortedByT7) + "通过T7面系数筛选出来。" + "T7面系数详情:" + t7Details.toString() + "。"; return new FollowBallMultiLevelFilteringResult(sortedByT7, stepsDesc, detailInfo); } // 检查是否存在边界相同面系数 double thresholdCoeff = num2Coeff.getOrDefault(sortedByT7.get(selectCount - 1), 0.0); // 统计与阈值系数相同的球号数量 long countAtThreshold = sortedByT7.stream() .mapToDouble(bn -> num2Coeff.getOrDefault(bn, 0.0)) .filter(c -> Double.compare(c, thresholdCoeff) == 0) .count(); // 如果阈值系数只有1个球号,说明没有边界重复,直接取前selectCount个 if (countAtThreshold == 1) { List directSelected = sortedByT7.subList(0, selectCount); completedSteps.add("通过T7面系数筛选确定"); skippedSteps.add("百期排位"); skippedSteps.add("历史排位"); skippedSteps.add("随机选择"); String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); String detailInfo = "T7面系数筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + formatBallNumbers(directSelected) + "通过T7面系数筛选出来。" + "T7面系数详情:" + t7Details.toString() + "。"; return new FollowBallMultiLevelFilteringResult(directSelected, stepsDesc, detailInfo); } // 存在边界相同面系数:拆分高于阈值与阈值相等的集合 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); } } List result = new ArrayList<>(higherThanThreshold); int stillNeed = selectCount - higherThanThreshold.size(); if (stillNeed > 0) { // 需要进一步筛选 FollowBallTop100HistoryFilteringResult furtherResult = selectByTop100ThenHistoryTopWithDetails(equalThresholdGroup, stillNeed); result.addAll(furtherResult.getSelectedBalls()); completedSteps.add("通过T7面系数和百期排位、历史排位筛选确定"); StringBuilder allDetails = new StringBuilder(); allDetails.append("T7面系数筛选:").append(formatBallNumbers(candidateBalls)).append("参与筛选,"); allDetails.append(formatBallNumbers(higherThanThreshold)).append("通过T7面系数直接筛选出来,"); allDetails.append(formatBallNumbers(equalThresholdGroup)).append("因T7面系数相同需要进入百期排位、历史排位筛选。"); allDetails.append("T7面系数详情:").append(t7Details.toString()).append("。"); if (!furtherResult.getDetailedInfo().isEmpty()) { allDetails.append(" ").append(furtherResult.getDetailedInfo()); } String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); return new FollowBallMultiLevelFilteringResult(result, stepsDesc, allDetails.toString()); } else { completedSteps.add("通过T7面系数筛选确定"); skippedSteps.add("百期排位"); skippedSteps.add("历史排位"); skippedSteps.add("随机选择"); String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); String detailInfo = "T7面系数筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + formatBallNumbers(result) + "通过T7面系数筛选出来。" + "T7面系数详情:" + t7Details.toString() + "。"; return new FollowBallMultiLevelFilteringResult(result, stepsDesc, detailInfo); } } /** * 跟随球号百期排位和历史排位筛选结果 */ private static class FollowBallTop100HistoryFilteringResult { private List selectedBalls; private String detailedInfo; public FollowBallTop100HistoryFilteringResult(List selectedBalls, String detailedInfo) { this.selectedBalls = selectedBalls; this.detailedInfo = detailedInfo; } public List getSelectedBalls() { return selectedBalls; } public String getDetailedInfo() { return detailedInfo; } } /** * 蓝球多级筛选结果内部类 */ private static class BlueBallMultiLevelFilteringResult { private List selectedBalls; private String stepsDescription; private String detailedStepsInfo; public BlueBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription) { this.selectedBalls = selectedBalls; this.stepsDescription = stepsDescription; this.detailedStepsInfo = ""; } public BlueBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription, String detailedStepsInfo) { this.selectedBalls = selectedBalls; this.stepsDescription = stepsDescription; this.detailedStepsInfo = detailedStepsInfo; } public List getSelectedBalls() { return selectedBalls; } public String getStepsDescription() { return stepsDescription; } public String getDetailedStepsInfo() { return detailedStepsInfo; } } /** * 通过百期排位和历史排位进行筛选(带详细信息) */ private FollowBallTop100HistoryFilteringResult selectByTop100ThenHistoryTopWithDetails(List candidateBalls, int needCount) { if (candidateBalls == null || candidateBalls.isEmpty() || needCount <= 0) { return new FollowBallTop100HistoryFilteringResult(new ArrayList<>(), ""); } StringBuilder detailedInfo = new StringBuilder(); // 在同T7系数的候选中,先用100期逐组加入 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<>(); StringBuilder top100Details = new StringBuilder(); if (top100List != null && !top100List.isEmpty()) { // 记录所有球号的百期排位详情 Map> top100Groups = new TreeMap<>(Collections.reverseOrder()); for (HistoryTop100 item : top100List) { top100Groups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()).add(item.getBallNumber()); } for (Map.Entry> entry : top100Groups.entrySet()) { List balls = entry.getValue(); Collections.sort(balls); if (top100Details.length() > 0) top100Details.append(","); top100Details.append(formatBallNumbers(balls)).append("(百期排位").append(String.format("%.2f", entry.getKey())).append(")"); } // 记录不在百期表中的球号 List notInTop100 = new ArrayList<>(candidateBalls); List inTop100 = top100List.stream().map(HistoryTop100::getBallNumber).collect(Collectors.toList()); notInTop100.removeAll(inTop100); if (!notInTop100.isEmpty()) { Collections.sort(notInTop100); if (top100Details.length() > 0) top100Details.append(","); top100Details.append(formatBallNumbers(notInTop100)).append("(无百期排位)"); } // 分组筛选 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) { detailedInfo.append("百期排位筛选:").append(formatBallNumbers(candidateBalls)).append("参与筛选,"); detailedInfo.append(formatBallNumbers(selectedByTop100.subList(0, needCount))).append("通过百期排位筛选出来。"); detailedInfo.append("百期排位详情:").append(top100Details.toString()).append("。"); return new FollowBallTop100HistoryFilteringResult(selectedByTop100.subList(0, needCount), detailedInfo.toString()); } // 还需要历史排位筛选 int stillNeed = needCount - selectedByTop100.size(); List forHistoryTop = !undecidedGroup.isEmpty() ? new ArrayList<>(undecidedGroup) : (top100List == null || top100List.isEmpty() ? new ArrayList<>(candidateBalls) : new ArrayList<>()); List selectedByHistoryTop = new ArrayList<>(); StringBuilder historyDetails = new StringBuilder(); if (!forHistoryTop.isEmpty()) { QueryWrapper historyQuery = new QueryWrapper<>(); historyQuery.in("ballNumber", forHistoryTop) .orderByDesc("pointCoefficient") .orderByAsc("ballNumber"); List historyTopList = historyTopMapper.selectList(historyQuery); if (historyTopList != null && !historyTopList.isEmpty()) { // 记录所有球号的历史排位详情 Map> historyGroups = new TreeMap<>(Collections.reverseOrder()); for (HistoryTop item : historyTopList) { historyGroups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()).add(item.getBallNumber()); } for (Map.Entry> entry : historyGroups.entrySet()) { List balls = entry.getValue(); Collections.sort(balls); if (historyDetails.length() > 0) historyDetails.append(","); historyDetails.append(formatBallNumbers(balls)).append("(历史点系数").append(String.format("%.2f", entry.getKey())).append(")"); } // 记录不在历史表中的球号 List notInHistory = new ArrayList<>(forHistoryTop); List inHistory = historyTopList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList()); notInHistory.removeAll(inHistory); if (!notInHistory.isEmpty()) { Collections.sort(notInHistory); if (historyDetails.length() > 0) historyDetails.append(","); historyDetails.append(formatBallNumbers(notInHistory)).append("(无历史数据)"); } // 按点系数筛选 selectedByHistoryTop.addAll(historyTopList.stream() .limit(stillNeed) .map(HistoryTop::getBallNumber) .collect(Collectors.toList())); } } List finalResult = new ArrayList<>(selectedByTop100); finalResult.addAll(selectedByHistoryTop); // 构建详细信息 detailedInfo.append("百期排位筛选:").append(formatBallNumbers(candidateBalls)).append("参与筛选,"); if (!selectedByTop100.isEmpty()) { detailedInfo.append(formatBallNumbers(selectedByTop100)).append("通过百期排位筛选出来,"); } if (!forHistoryTop.isEmpty()) { detailedInfo.append(formatBallNumbers(forHistoryTop)).append("因百期排位不足或相同需要进入历史排位筛选。"); } if (top100Details.length() > 0) { detailedInfo.append("百期排位详情:").append(top100Details.toString()).append("。"); } if (!forHistoryTop.isEmpty() && !selectedByHistoryTop.isEmpty()) { detailedInfo.append(" 历史排位筛选:").append(formatBallNumbers(forHistoryTop)).append("参与筛选,"); detailedInfo.append(formatBallNumbers(selectedByHistoryTop)).append("通过历史排位筛选出来。"); } if (historyDetails.length() > 0) { detailedInfo.append(" 历史排位详情:").append(historyDetails.toString()).append("。"); } return new FollowBallTop100HistoryFilteringResult(finalResult, detailedInfo.toString()); } /** * 构建跟随球号筛选步骤描述 */ private String buildFollowBallStepsDescription(List completedSteps, List skippedSteps) { StringBuilder sb = new StringBuilder(); if (!completedSteps.isEmpty()) { if (completedSteps.size() == 1) { sb.append("通过频率筛选确定部分球号,通过").append(completedSteps.get(0)).append("确定剩余球号"); } else { sb.append("通过频率筛选确定部分球号,经过"); for (int i = 0; i < completedSteps.size(); i++) { if (i > 0) sb.append("、"); sb.append(completedSteps.get(i)); } sb.append("最终确定剩余球号"); } } if (!skippedSteps.isEmpty()) { if (sb.length() > 0) sb.append(","); sb.append("无需进行").append(String.join("、", skippedSteps)); } return sb.append("。").toString(); } /** * 蓝球多级筛选(带详细过程说明) * @param candidateBalls 候选蓝球号码 * @param selectCount 需要选择的数量 * @param step2BlueT8Coefficients 第二步T8面系数数据 * @return 筛选结果和详细说明 */ private BlueBallMultiLevelFilteringResult performBlueBallMultiLevelFilteringWithDetails(List candidateBalls, int selectCount, List step2BlueT8Coefficients) { List completedSteps = new ArrayList<>(); List skippedSteps = new ArrayList<>(); // 统计同频候选在step2BlueT8Coefficients中的面系数和(找不到视为0) Map sumMap = new HashMap<>(); StringBuilder t8Details = new StringBuilder(); Map> t8Groups = new TreeMap<>(Collections.reverseOrder()); for (Integer ball : candidateBalls) { double sum = 0.0; if (step2BlueT8Coefficients != null) { for (BallWithCoefficient bwc : step2BlueT8Coefficients) { if (ball.equals(bwc.getBallNumber())) { sum += bwc.getCoefficient(); } } } sumMap.put(ball, sum); t8Groups.computeIfAbsent(sum, k -> new ArrayList<>()).add(ball); } // 记录所有候选球号的T8面系数和详情 for (Map.Entry> entry : t8Groups.entrySet()) { List balls = entry.getValue(); Collections.sort(balls); if (t8Details.length() > 0) t8Details.append(","); t8Details.append(formatBallNumbers(balls)).append("(T8面系数和").append(String.format("%.2f", entry.getKey())).append(")"); } // 按"系数和降序、球号升序"排序,取前selectCount个 List sortedCandidates = new ArrayList<>(candidateBalls); sortedCandidates.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 result = sortedCandidates.subList(0, Math.min(selectCount, sortedCandidates.size())); completedSteps.add("通过T8面系数和筛选确定"); skippedSteps.add("百期排位"); skippedSteps.add("历史排位"); String stepsDesc = buildBlueBallStepsDescription(completedSteps, skippedSteps); String detailInfo = "T8面系数和筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + formatBallNumbers(result) + "通过T8面系数和筛选出来。" + "T8面系数和详情:" + t8Details.toString() + "。"; return new BlueBallMultiLevelFilteringResult(result, stepsDesc, detailInfo); } /** * 构建蓝球筛选步骤描述 */ private String buildBlueBallStepsDescription(List completedSteps, List skippedSteps) { StringBuilder sb = new StringBuilder(); if (!completedSteps.isEmpty()) { if (completedSteps.size() == 1) { sb.append("通过频率筛选确定部分球号,通过").append(completedSteps.get(0)).append("确定剩余球号"); } else { sb.append("通过频率筛选确定部分球号,经过"); for (int i = 0; i < completedSteps.size(); i++) { if (i > 0) sb.append("、"); sb.append(completedSteps.get(i)); } sb.append("最终确定剩余球号"); } } if (!skippedSteps.isEmpty()) { if (sb.length() > 0) sb.append(","); sb.append("无需进行").append(String.join("、", skippedSteps)); } return sb.append("。").toString(); } /** * 格式化球号列表为字符串 */ private String formatBallNumbers(List ballNumbers) { if (ballNumbers == null || ballNumbers.isEmpty()) { return ""; } List sortedBalls = new ArrayList<>(ballNumbers); Collections.sort(sortedBalls); if (sortedBalls.size() <= 10) { return sortedBalls.toString().replace("[", "").replace("]", ""); } else { // 如果球号太多,只显示前几个和后几个 List displayBalls = new ArrayList<>(); displayBalls.addAll(sortedBalls.subList(0, 5)); displayBalls.addAll(sortedBalls.subList(sortedBalls.size() - 3, sortedBalls.size())); return displayBalls.subList(0, 5).toString().replace("[", "").replace("]", "") + "等" + sortedBalls.size() + "个"; } } /** * 格式化球号列表为字符串(完整显示,不使用"等"字简化) */ private String formatBallNumbersComplete(List ballNumbers) { if (ballNumbers == null || ballNumbers.isEmpty()) { return ""; } List sortedBalls = new ArrayList<>(ballNumbers); Collections.sort(sortedBalls); return sortedBalls.toString().replace("[", "").replace("]", ""); } /** * 格式化带频率的球号列表为字符串 * @param frequencyGroups 按频率分组的球号,key为频率,value为球号列表 */ private String formatBallNumbersWithFrequency(Map> frequencyGroups) { if (frequencyGroups == null || frequencyGroups.isEmpty()) { return ""; } StringBuilder result = new StringBuilder(); List parts = new ArrayList<>(); // 按频率从高到低排序 List>> sortedEntries = frequencyGroups.entrySet().stream() .sorted(Map.Entry.>comparingByKey().reversed()) .collect(Collectors.toList()); for (Map.Entry> entry : sortedEntries) { Integer frequency = entry.getKey(); List balls = entry.getValue(); // 对球号排序 Collections.sort(balls); // 始终显示所有球号,不使用省略 String ballsStr = balls.toString().replace("[", "").replace("]", ""); parts.add(ballsStr + "(出现" + frequency + "次)"); } // 组合所有部分,显示完整的频率分布信息 for (int i = 0; i < parts.size(); i++) { if (i > 0) { result.append(","); } result.append(parts.get(i)); } return result.toString(); } /** * 执行多层筛选并返回详细的步骤信息 * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @param ballsWithCoefficients 系数数据 * @return 筛选结果和步骤描述 */ private MultiLevelFilteringResult performMultiLevelFilteringWithDetails(List candidateBalls, int selectCount, List ballsWithCoefficients) { log.debug("开始详细多层筛选,候选球号:{},需要选择:{}个", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { return new MultiLevelFilteringResult(new ArrayList<>(candidateBalls), "直接选择,无需进一步筛选。"); } List currentCandidates = new ArrayList<>(candidateBalls); List completedSteps = new ArrayList<>(); List skippedSteps = new ArrayList<>(); StringBuilder detailedInfo = new StringBuilder(); // 第一层:ballNumbersWithCoefficients系数和筛选 log.debug("=== 第一层:T3系数和筛选 ==="); FilteringDetailResult t3Result = selectBallsByBallNumbersWithCoefficientsWithDetails(currentCandidates, selectCount, ballsWithCoefficients); List ballNumbersFiltered = t3Result.getFilteredBalls(); log.debug("T3系数和筛选结果:{}", ballNumbersFiltered); if (ballNumbersFiltered.size() < currentCandidates.size()) { completedSteps.add("T3系数和筛选"); if (!t3Result.getDetailInfo().isEmpty()) { detailedInfo.append("T3系数和详情:").append(t3Result.getDetailInfo()).append("。"); } } if (ballNumbersFiltered.size() == selectCount) { log.debug("T3系数和筛选完成,选择:{}", ballNumbersFiltered); skippedSteps.add("百期排位"); skippedSteps.add("历史排位"); return new MultiLevelFilteringResult(ballNumbersFiltered, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); } currentCandidates = ballNumbersFiltered; // 第二层:history_top_100表排名筛选(百期排位) log.debug("=== 第二层:百期排位筛选 ==="); FilteringDetailResult top100Result = selectBallsByHistoryTop100RankingWithDetails(currentCandidates, selectCount); List historyTop100Filtered = top100Result.getFilteredBalls(); log.debug("百期排位筛选结果:{}", historyTop100Filtered); if (historyTop100Filtered.size() < currentCandidates.size()) { completedSteps.add("百期排位"); if (!top100Result.getDetailInfo().isEmpty()) { if (detailedInfo.length() > 0) detailedInfo.append(" "); detailedInfo.append("百期排位详情:").append(top100Result.getDetailInfo()).append("。"); } } if (historyTop100Filtered.size() == selectCount) { log.debug("百期排位筛选完成,选择:{}", historyTop100Filtered); skippedSteps.add("历史排位"); return new MultiLevelFilteringResult(historyTop100Filtered, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); } currentCandidates = historyTop100Filtered; // 第三层:history_top表点系数筛选(历史排位) log.debug("=== 第三层:历史排位筛选 ==="); FilteringDetailResult historyTopResult = selectBallsByHistoryTopPointCoefficientWithDetails(currentCandidates, selectCount); List historyTopFiltered = historyTopResult.getFilteredBalls(); log.debug("历史排位筛选结果:{}", historyTopFiltered); if (historyTopFiltered.size() < currentCandidates.size()) { completedSteps.add("历史排位"); if (!historyTopResult.getDetailInfo().isEmpty()) { if (detailedInfo.length() > 0) detailedInfo.append(" "); detailedInfo.append("历史排位详情:").append(historyTopResult.getDetailInfo()).append("。"); } } if (historyTopFiltered.size() == selectCount) { log.debug("历史排位筛选完成,选择:{}", historyTopFiltered); return new MultiLevelFilteringResult(historyTopFiltered, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); } currentCandidates = historyTopFiltered; // 第四层:随机选择 log.debug("=== 第四层:随机选择 ==="); List randomSelected = selectBallsRandomly(currentCandidates, selectCount); log.debug("随机筛选完成,选择:{}", randomSelected); if (currentCandidates.size() > selectCount) { completedSteps.add("随机选择"); } return new MultiLevelFilteringResult(randomSelected, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); } /** * 构建筛选步骤描述 * @param completedSteps 已完成的步骤 * @param skippedSteps 跳过的步骤 * @return 步骤描述字符串 */ private String buildStepsDescription(List completedSteps, List skippedSteps) { StringBuilder description = new StringBuilder(); if (!completedSteps.isEmpty()) { if (completedSteps.size() == 1) { description.append("通过频率筛选确定部分球号,通过").append(completedSteps.get(0)).append("确定剩余球号"); } else { description.append("通过频率筛选确定部分球号,经过"); for (int i = 0; i < completedSteps.size(); i++) { if (i > 0) description.append("、"); description.append(completedSteps.get(i)); } description.append("最终确定剩余球号"); } } if (!skippedSteps.isEmpty()) { if (description.length() > 0) { description.append(",无需进行"); } else { description.append("无需进行"); } for (int i = 0; i < skippedSteps.size(); i++) { if (i > 0) description.append("、"); description.append(skippedSteps.get(i)); } } description.append("。"); return description.toString(); } /** * 根据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; } } /** * 根据ballNumbersWithCoefficients系数和进行筛选(包含详细信息) * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @param ballsWithCoefficients 系数数据 * @return 筛选结果和详细信息 */ private FilteringDetailResult selectBallsByBallNumbersWithCoefficientsWithDetails(List candidateBalls, int selectCount, List ballsWithCoefficients) { log.debug("使用ballNumbersWithCoefficients系数和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount); if (candidateBalls.size() <= selectCount) { return new FilteringDetailResult(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<>(); StringBuilder detailInfo = new StringBuilder(); // 先记录所有球号的系数和详细信息 for (Map.Entry> group : coefficientGroups.entrySet()) { Double coefficientSum = group.getKey(); List balls = group.getValue(); Collections.sort(balls); // 按球号升序排序 // 记录详细信息(显示所有参与筛选的球号) if (detailInfo.length() > 0) detailInfo.append(","); detailInfo.append(formatBallNumbers(balls)).append("(系数和").append(String.format("%.2f", coefficientSum)).append(")"); } // 再按系数和从高到低筛选球号 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 new FilteringDetailResult(result, detailInfo.toString()); } /** * 根据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", 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(); log.debug("T3表主球{}的线系数之和:{}", masterBallNumber, sum); return sum; } catch (Exception e) { log.error("计算T3表线系数之和时出错,主球号:{}", masterBallNumber, e); return 0.0; } } /** * 跟随球号分析算法 * @param level 高位/中位/低位标识 (H/M/L) * @param firstThreeRedBalls 前3个红球号码(独立的) * @param lastSixRedBalls 后6个红球号码(独立的) * @param blueBall 1个蓝球号码 * @return 分析结果:出现频率最高的前10位数字 */ public List fallowBallAnalysis(String level, List firstThreeRedBalls, List lastSixRedBalls, Integer blueBall) { log.info("开始跟随球号分析算法,级别:{},前3个红球:{},后6个红球:{},蓝球:{}", level, firstThreeRedBalls, lastSixRedBalls, blueBall); // 验证输入参数 if (firstThreeRedBalls == null || firstThreeRedBalls.size() != 3) { throw new IllegalArgumentException("前3个红球数量必须为3个"); } if (lastSixRedBalls == null || lastSixRedBalls.size() != 6) { throw new IllegalArgumentException("后6个红球数量必须为6个"); } if (blueBall == null) { throw new IllegalArgumentException("蓝球不能为空"); } if (!Arrays.asList("H", "M", "L").contains(level)) { throw new IllegalArgumentException("级别必须为H、M或L"); } // 验证球号范围和重复性 validateBallNumbers(firstThreeRedBalls, lastSixRedBalls, blueBall); List allNumbers = new ArrayList<>(); // 第一步:处理第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++) { Integer redBall = lastSixRedBalls.get(i); log.info("处理第{}个红球:{}", i + 1, redBall); List ballNumbers = getTop26FromT3(redBall); allNumbers.addAll(ballNumbers); log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers); } // 第三步:从history_top_100获取前3个球号 log.info("第三步:从history_top_100获取前3个球号"); List top3Numbers = getTop3FromHistoryTop100(); allNumbers.addAll(top3Numbers); log.info("从history_top_100获取到{}个数字:{}", top3Numbers.size(), top3Numbers); // 第四步:取出前3个红球号码的后两个 log.info("第四步:取出前3个红球号码的后两个"); List lastTwoOfFirstThree = Arrays.asList(firstThreeRedBalls.get(1), firstThreeRedBalls.get(2)); allNumbers.addAll(lastTwoOfFirstThree); log.info("前3个红球的后两个:{}", lastTwoOfFirstThree); // 第五步:用上期蓝球从T4表获取26个蓝球号码 log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", blueBall); List blueNumbers = getTop26FromT4(blueBall); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个数字", allNumbers.size()); // 第六步:统计频率并获取前10个(若同频超过10个,用第一步T7面系数作为二次筛选依据) List result = getTop10ByFrequency(allNumbers, step1BallsWithCoefficients); log.info("跟随球号分析算法完成,结果:{}", result); return result; } /** * 跟随球号分析算法(带筛选过程说明) * @param level 级别:H-高,M-中,L-低 * @param firstThreeRedBalls 前3个红球 * @param lastSixRedBalls 后6个红球 * @param blueBall 蓝球 * @return 分析结果VO,包含前10个数字和筛选过程说明 */ public FollowBallAnalysisResultVO fallowBallAnalysisWithProcess(String level, List firstThreeRedBalls, List lastSixRedBalls, Integer blueBall) { log.info("开始跟随球号分析算法(带过程说明),级别:{},前3个红球:{},后6个红球:{},蓝球:{}", level, firstThreeRedBalls, lastSixRedBalls, blueBall); // 验证输入参数 if (firstThreeRedBalls == null || firstThreeRedBalls.size() != 3) { throw new IllegalArgumentException("前3个红球数量必须为3个"); } if (lastSixRedBalls == null || lastSixRedBalls.size() != 6) { throw new IllegalArgumentException("后6个红球数量必须为6个"); } if (blueBall == null) { throw new IllegalArgumentException("蓝球不能为空"); } if (!Arrays.asList("H", "M", "L").contains(level)) { throw new IllegalArgumentException("级别必须为H、M或L"); } // 验证球号范围和重复性 validateBallNumbers(firstThreeRedBalls, lastSixRedBalls, blueBall); List allNumbers = new ArrayList<>(); // 第一步:处理第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++) { Integer redBall = lastSixRedBalls.get(i); log.info("处理第{}个红球:{}", i + 1, redBall); List ballNumbers = getTop26FromT3(redBall); allNumbers.addAll(ballNumbers); log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers); } // 第三步:从history_top_100获取前3个球号 log.info("第三步:从history_top_100获取前3个球号"); List top3Numbers = getTop3FromHistoryTop100(); allNumbers.addAll(top3Numbers); log.info("从history_top_100获取到{}个数字:{}", top3Numbers.size(), top3Numbers); // 第四步:取出前3个红球号码的后两个 log.info("第四步:取出前3个红球号码的后两个"); List lastTwoOfFirstThree = Arrays.asList(firstThreeRedBalls.get(1), firstThreeRedBalls.get(2)); allNumbers.addAll(lastTwoOfFirstThree); log.info("前3个红球的后两个:{}", lastTwoOfFirstThree); // 第五步:用上期蓝球从T4表获取26个蓝球号码 log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", blueBall); List blueNumbers = getTop26FromT4(blueBall); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个数字", allNumbers.size()); // 第六步:统计频率并获取前10个(带筛选过程说明) FollowBallAnalysisResultVO result = getTop10ByFrequencyWithProcess(allNumbers, step1BallsWithCoefficients); log.info("跟随球号分析算法(带过程说明)完成,结果:{},过程说明:{}", result.getResult(), result.getFilteringProcess()); return result; } /** * 验证球号的有效性和唯一性 */ private void validateBallNumbers(List firstThreeRedBalls, List lastSixRedBalls, Integer blueBall) { // 验证红球范围 for (Integer ball : firstThreeRedBalls) { if (ball == null || ball < 1 || ball > 33) { throw new IllegalArgumentException("前3个红球号码必须在1-33范围内"); } } for (Integer ball : lastSixRedBalls) { if (ball == null || ball < 1 || ball > 33) { throw new IllegalArgumentException("后6个红球号码必须在1-33范围内"); } } // 验证蓝球范围 if (blueBall < 1 || blueBall > 16) { throw new IllegalArgumentException("蓝球号码必须在1-16范围内"); } // 验证前3个红球不重复 Set firstThreeSet = new HashSet<>(firstThreeRedBalls); if (firstThreeSet.size() != 3) { throw new IllegalArgumentException("前3个红球号码不能重复"); } // 验证后6个红球不重复 Set lastSixSet = new HashSet<>(lastSixRedBalls); if (lastSixSet.size() != 6) { throw new IllegalArgumentException("后6个红球号码不能重复"); } log.debug("球号验证通过"); } /** * 从T7表获取指定主球的10个从球号 * 根据不同级别使用不同的选择策略 * @param masterBallNumber 主球号 * @param level 级别:H-高位,M-中位,L-低位 */ private List getTop10FromT7(Integer masterBallNumber, String level) { log.debug("从T7表查询主球{}的面系数数据,级别:{}", masterBallNumber, level); // 查询指定主球的所有数据,按面系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("faceCoefficient") .orderByAsc("slaveBallNumber"); // 面系数相同时按从球号升序 List t7List = t7Mapper.selectList(queryWrapper); if (t7List.isEmpty()) { log.warn("T7表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } List result = new ArrayList<>(); switch (level) { case "H": // 高位:取面系数最大的前10个 result = getHighLevelBallsFromT7(t7List); break; case "M": // 中位:取面系数平均值向上5个、向下4个,共10个球号 result = getMiddleLevelBallsFromT7(t7List); break; case "L": // 低位:取面系数最小值向上第3-13个球共10个球号 result = getLowLevelBallsFromT7(t7List); break; } log.debug("T7表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result); return result; } /** * 高位算法:从T7表取面系数最大的前10个 */ private List getHighLevelBallsFromT7(List t7List) { if (t7List.size() < 10) { log.warn("T7表数据不足10条,实际{}条", t7List.size()); return t7List.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前9个 List result = new ArrayList<>(); for (int i = 0; i < 9; i++) { result.add(t7List.get(i).getSlaveBallNumber()); } // 处理第10个位置:检查是否有相同的面系数 Double targetCoefficient = t7List.get(9).getFaceCoefficient(); List candidatesFor10th = new ArrayList<>(); // 找出所有面系数等于第10个位置面系数的记录 for (int i = 9; i < t7List.size(); i++) { if (t7List.get(i).getFaceCoefficient().equals(targetCoefficient)) { candidatesFor10th.add(t7List.get(i)); } else { break; // 面系数不同,停止查找 } } if (candidatesFor10th.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor10th.get(0).getSlaveBallNumber()); } else { // 有多个候选,通过history_top_100表的点系数排序 log.debug("第10位有{}个相同面系数的候选:{}", candidatesFor10th.size(), candidatesFor10th.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( candidatesFor10th.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } log.debug("T7高位算法最终选择的10个球:{}", result); return result; } /** * 中位算法:从T7表取面系数平均值向上5个、向下4个,共10个球号。 * 毛边处理要求: * - 向上边界包含边界本身(或其上方/下方的同系数项)一起参与比较; * - 向下边界包含边界本身(或其下方的同系数项)一起参与比较; * - 当边界存在多个相同面系数的候选时,按 history_top_100 → history_top → 随机 的顺序筛选。 */ private List getMiddleLevelBallsFromT7(List t7List) { if (t7List.size() < 10) { log.warn("T7表数据不足10条,实际{}条", t7List.size()); return t7List.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()); } // 计算面系数平均值 double avgCoefficient = t7List.stream() .mapToDouble(T7::getFaceCoefficient) .average() .orElse(0.0); log.debug("T7表面系数平均值:{}", avgCoefficient); // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) int avgIndex = -1; double minDiff = Double.MAX_VALUE; for (int i = 0; i < t7List.size(); i++) { double coefficient = t7List.get(i).getFaceCoefficient(); // 只考虑大于平均值的值 if (coefficient > avgCoefficient) { double diff = coefficient - avgCoefficient; if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 minDiff = diff; avgIndex = i; } } } // 如果没有找到大于平均值的,则取最大值 if (avgIndex == -1) { avgIndex = 0; // T7表按面系数降序排列,第一个就是最大值 log.debug("未找到大于平均值的位置,使用最大值位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient()); } else { log.debug("找到大于平均值且最接近的位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient()); } // 向上5个,向下4个,共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)) { if (startIndex > 0) { startIndex--; } if (endIndex < t7List.size() - 1 && endIndex - startIndex + 1 < 10) { endIndex++; } } List candidates = new ArrayList<>(); for (int i = startIndex; i <= endIndex && candidates.size() < 10; i++) { candidates.add(t7List.get(i).getSlaveBallNumber()); } // 处理边界位置的相同面系数情况(包含边界本身参与比较,并带回退规则) List result = handleT7BoundaryConflicts(t7List, candidates, startIndex, endIndex); log.debug("T7中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); return result; } /** * 低位算法:从T7表取面系数最小值向上第3-13个球共10个球号 */ private List getLowLevelBallsFromT7(List t7List) { if (t7List.size() < 13) { log.warn("T7表数据不足13条,实际{}条", t7List.size()); return t7List.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()); } // 从最后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++) { candidates.add(t7List.get(i).getSlaveBallNumber()); } // 处理边界位置的相同面系数情况 List result = handleT7BoundaryConflicts(t7List, candidates, startIndex, endIndex); log.debug("T7低位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); return result; } /** * 处理T7表边界位置的面系数冲突 */ private List handleT7BoundaryConflicts(List t7List, List candidates, int startIndex, int endIndex) { List result = new ArrayList<>(candidates); if (result.size() >= 10) { // 检查第1个位置(最大面系数)的冲突:包含边界本身 Double firstCoefficient = t7List.get(startIndex).getFaceCoefficient(); List candidatesForFirst = new ArrayList<>(); candidatesForFirst.add(t7List.get(startIndex)); // 边界本身 // 找出第一个位置上面(如果存在)的所有相同面系数的记录 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(), candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(0, bestBall); } // 检查第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++) { if (t7List.get(i).getFaceCoefficient().equals(lastCoefficient)) { candidatesForLast.add(t7List.get(i)); } } if (candidatesForLast.size() > 1) { log.debug("第10位及其下面有{}个相同面系数的候选:{}", candidatesForLast.size(), candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallWithFallback( candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()) ); result.set(result.size() - 1, bestBall); } } return result.subList(0, Math.min(10, result.size())); } /** * 从T3表获取指定主球的26个从球号(按线系数从大到小排列) */ private List getTop26FromT3(Integer masterBallNumber) { log.debug("从T3表查询主球{}的线系数数据,取前26个", masterBallNumber); // 查询指定主球的所有数据,按线系数降序排列 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<>(); } if (t3List.size() < 26) { log.warn("T3表数据不足26条,实际{}条", t3List.size()); return t3List.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前25个 List result = new ArrayList<>(); for (int i = 0; i < 25; i++) { result.add(t3List.get(i).getSlaveBallNumber()); } // 处理第26个位置:检查是否有相同的线系数 Double targetCoefficient = t3List.get(25).getLineCoefficient(); List candidatesFor26th = new ArrayList<>(); // 找出所有线系数等于第26个位置线系数的记录 for (int i = 25; i < t3List.size(); i++) { if (t3List.get(i).getLineCoefficient().equals(targetCoefficient)) { candidatesFor26th.add(t3List.get(i)); } else { break; // 线系数不同,停止查找 } } if (candidatesFor26th.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor26th.get(0).getSlaveBallNumber()); } else { // 有多个候选,通过history_top_100表的点系数排序 log.debug("第26位有{}个相同线系数的候选:{}", candidatesFor26th.size(), candidatesFor26th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( candidatesFor26th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } log.debug("T3表主球{}最终选择的26个从球:{}", masterBallNumber, result); return result; } /** * 从history_top_100表获取前3个球号(按点系数排序) */ private List getTop3FromHistoryTop100() { log.debug("从history_top_100表获取前3个球号"); // 查询前3个点系数最高的球号 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("pointCoefficient") .orderByAsc("ballNumber") // 点系数相同时按球号升序 .last("LIMIT 3"); // 限制只返回前3条记录 List top100List = historyTop100Mapper.selectList(queryWrapper); if (top100List.size() < 3) { log.warn("history_top_100表数据不足3条,实际{}条", top100List.size()); } List result = top100List.stream() .map(HistoryTop100::getBallNumber) .collect(Collectors.toList()); log.debug("history_top_100表前3个球号:{}", result); return result; } /** * 从T4表获取指定主球的10个从球号 * 根据不同级别使用不同的选择策略 * @param masterBallNumber 主球号 * @param level 级别:H-高位,M-中位,L-低位 */ private List getTop10FromT4(Integer masterBallNumber, String level) { log.debug("从T4表查询主球{}的线系数数据,级别:{}", masterBallNumber, level); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("lineCoefficient") .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 List t4List = t4Mapper.selectList(queryWrapper); if (t4List.isEmpty()) { log.warn("T4表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } List result = new ArrayList<>(); switch (level) { case "H": // 高位:取线系数最大的前10个 result = getHighLevelBallsFromT4For10(t4List); break; case "M": // 中位:取线系数平均值向上6个向下4个的10个球号 result = getMiddleLevelBallsFromT4For10(t4List); break; case "L": // 低位:取线系数最小值向上第4-14个球的10个球号 result = getLowLevelBallsFromT4For10(t4List); break; } log.debug("T4表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result); return result; } /** * 高位算法:从T4表取线系数最大的前10个 */ private List getHighLevelBallsFromT4For10(List t4List) { if (t4List.size() < 10) { log.warn("T4表数据不足10条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前9个 List result = new ArrayList<>(); for (int i = 0; i < 9; i++) { result.add(t4List.get(i).getSlaveBallNumber()); } // 处理第10个位置:检查是否有相同的线系数 Double targetCoefficient = t4List.get(9).getLineCoefficient(); List candidatesFor10th = new ArrayList<>(); // 找出所有线系数等于第10个位置线系数的记录 for (int i = 9; i < t4List.size(); i++) { if (t4List.get(i).getLineCoefficient().equals(targetCoefficient)) { candidatesFor10th.add(t4List.get(i)); } else { break; // 线系数不同,停止查找 } } if (candidatesFor10th.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor10th.get(0).getSlaveBallNumber()); } else { // 有多个候选,通过history_top_100表的点系数排序 log.debug("第10位有{}个相同线系数的候选:{}", candidatesFor10th.size(), candidatesFor10th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( candidatesFor10th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } log.debug("T4高位算法最终选择的10个球:{}", result); return result; } /** * 中位算法:从T4表取线系数平均值向上5个向下5个的10个球号 */ private List getMiddleLevelBallsFromT4For10(List t4List) { if (t4List.size() < 10) { log.warn("T4表数据不足10条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } // 计算线系数平均值 double avgCoefficient = t4List.stream() .mapToDouble(T4::getLineCoefficient) .average() .orElse(0.0); log.debug("T4表线系数平均值:{}", avgCoefficient); // 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个) int avgIndex = -1; double minDiff = Double.MAX_VALUE; for (int i = 0; i < t4List.size(); i++) { double coefficient = t4List.get(i).getLineCoefficient(); // 只考虑大于平均值的值 if (coefficient > avgCoefficient) { double diff = coefficient - avgCoefficient; if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值 minDiff = diff; avgIndex = i; } } } // 如果没有找到大于平均值的,则取最大值 if (avgIndex == -1) { avgIndex = 0; // T4表按线系数降序排列,第一个就是最大值 log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); } else { log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient()); } // 向上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)) { if (startIndex > 0) { startIndex--; } if (endIndex < t4List.size() - 1 && endIndex - startIndex + 1 < 10) { endIndex++; } } List candidates = new ArrayList<>(); for (int i = startIndex; i <= endIndex && candidates.size() < 10; i++) { candidates.add(t4List.get(i).getSlaveBallNumber()); } // 处理边界位置的相同线系数情况 List result = handleT4BoundaryConflictsFor10(t4List, candidates, startIndex, endIndex); log.debug("T4中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); return result; } /** * 低位算法:从T4表取线系数最小值向上第4-14个球的10个球号 */ private List getLowLevelBallsFromT4For10(List t4List) { if (t4List.size() < 14) { log.warn("T4表数据不足14条,实际{}条", 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; List candidates = new ArrayList<>(); for (int i = startIndex; i <= endIndex; i++) { candidates.add(t4List.get(i).getSlaveBallNumber()); } // 处理边界位置的相同线系数情况 List result = handleT4BoundaryConflictsFor10(t4List, candidates, startIndex, endIndex); log.debug("T4低位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size()); return result; } /** * 统计数字出现频率,返回频率最高的前10个数字 * 如果频次相同的球号超过10个,使用T7表面系数进行二次筛选 */ private List getTop10ByFrequency(List allNumbers, List step1BallsWithCoefficients) { log.debug("统计{}个数字的出现频率", allNumbers.size()); // 统计频率 Map frequencyMap = new HashMap<>(); for (Integer number : allNumbers) { frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); } log.debug("数字频率统计:{}", frequencyMap); // 按频率分组 Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : frequencyMap.entrySet()) { Integer frequency = entry.getValue(); Integer ballNumber = entry.getKey(); frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); } log.debug("按频率分组:{}", frequencyGroups); List result = new ArrayList<>(); // 按频率从高到低处理 for (Map.Entry> group : frequencyGroups.entrySet()) { Integer frequency = group.getKey(); List balls = group.getValue(); // 对同频率的球号按数字升序排序 Collections.sort(balls); log.info("频率{}的球号:{}", frequency, balls); // 检查加入这组球号后是否会超过10个 if (result.size() + balls.size() <= 10) { // 不会超过10个,直接添加所有球号 result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { // 会超过10个:先按第一步T7面系数排序;如仍有边界相同面系数,继续按 100期 → 历史 → 随机 的回退规则,只在边界同系数的集合内决策 int remainingSlots = 10 - result.size(); 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()); } } // 先按面系数降序,再按球号升序 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) { return Double.compare(cb, ca); } return Integer.compare(a, b); }); // 如果正好能取齐,直接取 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个,结束 if (result.size() >= 8) { break; } } log.info("频率统计最终结果(共{}个):{}", result.size(), result); // 打印详细的频率信息 for (int i = 0; i < result.size(); i++) { Integer ballNumber = result.get(i); Integer frequency = frequencyMap.get(ballNumber); log.info("第{}位:数字{},出现{}次", i + 1, ballNumber, frequency); } return result; } /** * 统计频率并获取前10个(带筛选过程说明) * @param allNumbers 所有数字 * @param step1BallsWithCoefficients 第一步T7面系数数据 * @return 包含结果和筛选过程说明的VO对象 */ private FollowBallAnalysisResultVO getTop10ByFrequencyWithProcess(List allNumbers, List step1BallsWithCoefficients) { log.debug("统计{}个数字的出现频率", allNumbers.size()); // 统计频率 Map frequencyMap = new HashMap<>(); for (Integer number : allNumbers) { frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); } log.debug("数字频率统计:{}", frequencyMap); // 按频率分组 Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : frequencyMap.entrySet()) { Integer frequency = entry.getValue(); Integer ballNumber = entry.getKey(); frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); } log.debug("按频率分组:{}", frequencyGroups); List result = new ArrayList<>(); List directlySelectedBalls = new ArrayList<>(); List multiLevelCandidates = new ArrayList<>(); List secondaryFilteredBalls = new ArrayList<>(); boolean needMultiLevelFiltering = false; StringBuilder processDescription = new StringBuilder(); String detailedStepsInfo = ""; // 按频率从高到低处理 for (Map.Entry> group : frequencyGroups.entrySet()) { Integer frequency = group.getKey(); List balls = group.getValue(); // 对同频率的球号按数字升序排序 Collections.sort(balls); log.info("频率{}的球号:{}", frequency, balls); // 检查加入这组球号后是否会超过10个 if (result.size() + balls.size() <= 10) { // 不会超过10个,直接添加所有球号 result.addAll(balls); directlySelectedBalls.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { // 会超过10个,需要多级筛选 needMultiLevelFiltering = true; multiLevelCandidates.addAll(balls); int remainingSlots = 10 - result.size(); log.info("需要从{}个频率相同的球号中选择{}个,先按第一步T7面系数进行筛选", balls.size(), remainingSlots); // 进行多级筛选 FollowBallMultiLevelFilteringResult filteringResult = performFollowBallMultiLevelFilteringWithDetails(balls, remainingSlots, step1BallsWithCoefficients); result.addAll(filteringResult.getSelectedBalls()); secondaryFilteredBalls = new ArrayList<>(filteringResult.getSelectedBalls()); processDescription.append("筛选步骤:").append(filteringResult.getStepsDescription()); detailedStepsInfo = filteringResult.getDetailedStepsInfo(); // 已达到10个,结束 break; } // 如果已经有10个,结束 if (result.size() >= 10) { break; } } // 构建详细的过程描述 StringBuilder detailedDescription = new StringBuilder(); // 1. 频率分布描述 - 只包含实际参与筛选的球号 List allParticipatedBalls = new ArrayList<>(); allParticipatedBalls.addAll(directlySelectedBalls); allParticipatedBalls.addAll(multiLevelCandidates); Map> participatedFrequencyGroups = new LinkedHashMap<>(); for (Integer ball : allParticipatedBalls) { Integer frequency = frequencyMap.get(ball); participatedFrequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ball); } // 按频率从高到低排序 Map> sortedParticipatedFrequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry> entry : participatedFrequencyGroups.entrySet()) { Integer frequency = entry.getKey(); List balls = new ArrayList<>(entry.getValue()); Collections.sort(balls); // 球号按升序排序 sortedParticipatedFrequencyGroups.put(frequency, balls); } detailedDescription.append("参与筛选的候选球号按频率分布为"); detailedDescription.append(formatBallNumbersWithFrequency(sortedParticipatedFrequencyGroups)); // 2. 筛选结果描述 if (needMultiLevelFiltering) { detailedDescription.append(";无法直接筛选出前10个,其中"); detailedDescription.append(formatBallNumbers(directlySelectedBalls)); detailedDescription.append("直接入选,"); detailedDescription.append(formatBallNumbers(multiLevelCandidates)); detailedDescription.append("需要进行二次筛选,最终筛选出"); detailedDescription.append(formatBallNumbers(secondaryFilteredBalls)); detailedDescription.append(",组成前10个球号:"); detailedDescription.append(formatBallNumbers(result)); detailedDescription.append("。"); detailedDescription.append(processDescription.toString()); if (!detailedStepsInfo.isEmpty()) { detailedDescription.append(" ").append(detailedStepsInfo); } } else { detailedDescription.append(";直接按频率筛选出前10个球号:"); detailedDescription.append(formatBallNumbers(result)); detailedDescription.append("。筛选步骤:通过频率筛选确定所有球号,无需进行T7面系数筛选、百期排位、历史排位。"); } return FollowBallAnalysisResultVO.builder() .result(result) .filteringProcess(detailedDescription.toString()) .build(); } /** * 根据T7表的面系数总和选择球号,支持多级筛选 * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @return 选择的球号列表 */ private List selectBallsByT7FaceCoefficient(List candidateBalls, int selectCount) { log.info("开始根据T7表面系数总和选择球号,候选:{},需要选择:{}", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号"); return new ArrayList<>(candidateBalls); } // 第一级:计算每个球号作为主球时的面系数总和 Map faceCoefficientSumMap = new HashMap<>(); for (Integer ballNumber : candidateBalls) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", ballNumber); List t7List = t7Mapper.selectList(queryWrapper); double sum = t7List.stream() .mapToDouble(T7::getFaceCoefficient) .sum(); faceCoefficientSumMap.put(ballNumber, sum); log.debug("球号{}作为主球的面系数总和:{}", ballNumber, sum); } // 按面系数总和分组 Map> faceCoefficientGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : faceCoefficientSumMap.entrySet()) { Double sum = entry.getValue(); Integer ballNumber = entry.getKey(); faceCoefficientGroups.computeIfAbsent(sum, k -> new ArrayList<>()).add(ballNumber); } log.info("T7表面系数总和分组:{}", faceCoefficientGroups); List result = new ArrayList<>(); // 按面系数总和从高到低处理 for (Map.Entry> group : faceCoefficientGroups.entrySet()) { Double faceSum = group.getKey(); List balls = group.getValue(); log.info("面系数总和{}的球号:{}", faceSum, 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 = selectBallsByMultiLevelFilteringFor8(balls, remainingSlots); result.addAll(selectedBalls); log.info("通过多级筛选完成,最终选择:{}", selectedBalls); break; // 已经达到selectCount个,结束 } // 如果已经有selectCount个,结束 if (result.size() >= selectCount) { break; } } log.info("T7表面系数筛选最终结果(共{}个):{}", result.size(), result); // 打印详细的面系数信息 for (int i = 0; i < result.size(); i++) { Integer ballNumber = result.get(i); Double sum = faceCoefficientSumMap.get(ballNumber); log.info("第{}位:球号{},面系数总和:{}", i + 1, ballNumber, sum); } return result; } /** * 多级筛选球号(8个球版本):history_top_100 -> history_top -> 随机选择 * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @return 选择的球号列表 */ private List selectBallsByMultiLevelFilteringFor8(List candidateBalls, int selectCount) { log.info("开始多级筛选(8个球版本),候选:{},需要选择:{}", 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 { // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 result.addAll(balls); log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); 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_100表的排名筛选球号(包含详细信息) * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @return 筛选结果和详细信息 */ private FilteringDetailResult selectBallsByHistoryTop100RankingWithDetails(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 new FilteringDetailResult(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<>(); StringBuilder detailInfo = new StringBuilder(); // 先记录所有球号的排位详细信息 for (Map.Entry> group : pointCoefficientGroups.entrySet()) { Double pointCoefficient = group.getKey(); List balls = group.getValue(); Collections.sort(balls); // 按球号升序排序 // 记录详细信息(显示所有参与筛选的球号) if (detailInfo.length() > 0) detailInfo.append(","); detailInfo.append(formatBallNumbers(balls)).append("(排位").append(String.format("%.2f", pointCoefficient)).append(")"); } // 再按点系数从高到低筛选球号 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 { // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 result.addAll(balls); log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); break; } // 如果已经有selectCount个,结束 if (result.size() >= selectCount) { break; } } // 处理不在history_top_100表中的球号 List notInTop100 = new ArrayList<>(candidateBalls); List inTop100 = new ArrayList<>(); for (HistoryTop100 item : top100List) { inTop100.add(item.getBallNumber()); } notInTop100.removeAll(inTop100); // 先记录所有不在表中的球号的详细信息 if (!notInTop100.isEmpty()) { Collections.sort(notInTop100); if (detailInfo.length() > 0) detailInfo.append(","); detailInfo.append(formatBallNumbers(notInTop100)).append("(无排位)"); } // 再选择需要的球号加入结果 if (!notInTop100.isEmpty() && result.size() < selectCount) { int remainingSlots = selectCount - result.size(); 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 new FilteringDetailResult(result, detailInfo.toString()); } /** * 根据history_top表的点系数筛选球号 * 当点系数相同时,返回所有相同点系数的球号,让下一层筛选来处理 */ 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 { // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 result.addAll(balls); log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); 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; } /** * 根据history_top表的点系数筛选球号(包含详细信息) * @param candidateBalls 候选球号列表 * @param selectCount 需要选择的数量 * @return 筛选结果和详细信息 */ private FilteringDetailResult selectBallsByHistoryTopPointCoefficientWithDetails(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 new FilteringDetailResult(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<>(); StringBuilder detailInfo = new StringBuilder(); // 先记录所有球号的点系数详细信息 for (Map.Entry> group : pointCoefficientGroups.entrySet()) { Double pointCoefficient = group.getKey(); List balls = group.getValue(); Collections.sort(balls); // 按球号升序排序 // 记录详细信息(显示所有参与筛选的球号) if (detailInfo.length() > 0) detailInfo.append(","); detailInfo.append(formatBallNumbers(balls)).append("(点系数").append(String.format("%.2f", pointCoefficient)).append(")"); } // 处理不在history_top表中的球号的详细信息 List notInHistoryTop = new ArrayList<>(candidateBalls); List inHistoryTop = new ArrayList<>(); for (HistoryTop item : historyTopList) { inHistoryTop.add(item.getBallNumber()); } notInHistoryTop.removeAll(inHistoryTop); // 先记录所有不在表中的球号的详细信息 if (!notInHistoryTop.isEmpty()) { Collections.sort(notInHistoryTop); if (detailInfo.length() > 0) detailInfo.append(","); detailInfo.append(formatBallNumbers(notInHistoryTop)).append("(无历史数据)"); } // 再按点系数从高到低筛选球号 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 { // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 result.addAll(balls); log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); break; } // 如果已经有selectCount个,结束 if (result.size() >= selectCount) { break; } } // 再选择需要的不在表中的球号加入结果 if (!notInHistoryTop.isEmpty() && result.size() < selectCount) { int remainingSlots = selectCount - result.size(); 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 new FilteringDetailResult(result, detailInfo.toString()); } /** * 随机选择球号 * @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; } /** * 仅在一组同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) * @param predictedRedBalls 6个预测的红球号码 * @param predictedBlueBalls 2个预测的蓝球号码 * @param lastRedBalls 6个上期红球号码 * @param lastBlueBall 1个上期蓝球号码 * @return 分析结果:出现频率最高的前4个蓝球号码 */ public List blueBallAnalysis(String level, List predictedRedBalls, List predictedBlueBalls, List lastRedBalls, Integer lastBlueBall) { log.info("开始蓝球分析算法,级别:{},预测红球:{},预测蓝球:{},上期红球:{},上期蓝球:{}", level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall); // 验证输入参数 validateBlueBallAnalysisParams(level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall); List allNumbers = new ArrayList<>(); // 第一步:处理上期6个红球,每个红球从T6表获取12个蓝球号码 log.info("第一步:处理上期6个红球,从T6表获取蓝球号码"); for (int i = 0; i < lastRedBalls.size(); i++) { Integer redBall = lastRedBalls.get(i); log.info("处理第{}个上期红球:{}", i + 1, redBall); List ballNumbers = getTop12FromT6(redBall); allNumbers.addAll(ballNumbers); log.info("上期红球{}从T6表获取到{}个蓝球号码:{}", redBall, ballNumbers.size(), ballNumbers); } // 第二步:处理预测的6个红球,每个红球从T8表获取5个蓝球号码 log.info("第二步:处理预测的6个红球,从T8表获取蓝球号码,使用{}级别算法", level); 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); } // 第三步:从blue_history_top_100获取前2个蓝球号码 log.info("第三步:从blue_history_top_100获取前2个蓝球号码"); List top2BlueNumbers = getTop2FromBlueHistoryTop100(); allNumbers.addAll(top2BlueNumbers); log.info("从blue_history_top_100获取到{}个蓝球号码:{}", top2BlueNumbers.size(), top2BlueNumbers); // 第四步:添加预测的2个蓝球号码 log.info("第四步:添加预测的2个蓝球号码:{}", predictedBlueBalls); allNumbers.addAll(predictedBlueBalls); // 第五步:用上期蓝球从T5表获取12个蓝球号码(含毛边处理) log.info("第五步:用上期蓝球{}从T5表获取12个蓝球号码", lastBlueBall); List blueNumbers = getTop12FromT5(lastBlueBall); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个蓝球号码", allNumbers.size()); // 汇总第二步收集的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; } /** * 蓝球分析算法主方法(带筛选过程说明) * @param level 高位/中位/低位标识 (H/M/L) * @param predictedRedBalls 6个预测的红球号码 * @param predictedBlueBalls 2个预测的蓝球号码 * @param lastRedBalls 6个上期红球号码 * @param lastBlueBall 1个上期蓝球号码 * @return 分析结果:出现频率最高的前4个蓝球号码及筛选过程说明 */ public BlueBallAnalysisResultVO blueBallAnalysisWithProcess(String level, List predictedRedBalls, List predictedBlueBalls, List lastRedBalls, Integer lastBlueBall) { log.info("开始蓝球分析算法(带过程说明),级别:{},预测红球:{},预测蓝球:{},上期红球:{},上期蓝球:{}", level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall); // 验证输入参数 validateBlueBallAnalysisParams(level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall); List allNumbers = new ArrayList<>(); // 第一步:处理上期6个红球,每个红球从T6表获取12个蓝球号码 log.info("第一步:处理上期6个红球,从T6表获取蓝球号码"); for (int i = 0; i < lastRedBalls.size(); i++) { Integer redBall = lastRedBalls.get(i); log.info("处理第{}个上期红球:{}", i + 1, redBall); List ballNumbers = getTop12FromT6(redBall); allNumbers.addAll(ballNumbers); log.info("上期红球{}从T6表获取到{}个蓝球号码:{}", redBall, ballNumbers.size(), ballNumbers); } // 第二步:处理预测的6个红球,每个红球从T8表获取5个蓝球号码 log.info("第二步:处理预测的6个红球,从T8表获取蓝球号码,使用{}级别算法", level); 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); } // 第三步:从blue_history_top_100获取前2个蓝球号码 log.info("第三步:从blue_history_top_100获取前2个蓝球号码"); List top2BlueNumbers = getTop2FromBlueHistoryTop100(); allNumbers.addAll(top2BlueNumbers); log.info("从blue_history_top_100获取到{}个蓝球号码:{}", top2BlueNumbers.size(), top2BlueNumbers); // 第四步:添加预测的2个蓝球号码 log.info("第四步:添加预测的2个蓝球号码:{}", predictedBlueBalls); allNumbers.addAll(predictedBlueBalls); // 第五步:用上期蓝球从T5表获取12个蓝球号码(含毛边处理) log.info("第五步:用上期蓝球{}从T5表获取12个蓝球号码", lastBlueBall); List blueNumbers = getTop12FromT5(lastBlueBall); allNumbers.addAll(blueNumbers); log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers); log.info("总共收集到{}个蓝球号码", allNumbers.size()); // 汇总第二步收集的T8面系数,供第六步同频二次筛选使用 List step2BlueT8Coefficients = new ArrayList<>(); // 由于上面的循环内使用了临时变量 t8Coeffs,这里重新按预测红球聚合一次,确保完整 for (Integer redBall : predictedRedBalls) { List tmpBalls = getSimpleTop5FromT8ByLevel(redBall, level); step2BlueT8Coefficients.addAll(getT8CoefficientsFor(redBall, tmpBalls)); } // 第六步:统计频率并获取前4个(带筛选过程说明) BlueBallAnalysisResultVO result = getSimpleTop4ByFrequencyWithProcess(allNumbers, step2BlueT8Coefficients); log.info("蓝球分析算法(带过程说明)完成,结果:{},过程说明:{}", result.getResult(), result.getFilteringProcess()); return result; } /** * 验证蓝球分析算法的输入参数 */ private void validateBlueBallAnalysisParams(String level, List predictedRedBalls, List predictedBlueBalls, List lastRedBalls, Integer lastBlueBall) { // 验证级别 if (!Arrays.asList("H", "M", "L").contains(level)) { throw new IllegalArgumentException("级别必须为H、M或L"); } // 验证预测红球 if (predictedRedBalls == null || predictedRedBalls.size() != 6) { throw new IllegalArgumentException("预测红球数量必须为6个"); } for (Integer ball : predictedRedBalls) { if (ball == null || ball < 1 || ball > 33) { throw new IllegalArgumentException("预测红球号码必须在1-33范围内,实际:" + ball); } } if (predictedRedBalls.stream().distinct().count() != predictedRedBalls.size()) { throw new IllegalArgumentException("预测红球号码不能重复"); } // 验证预测蓝球 if (predictedBlueBalls == null || predictedBlueBalls.size() != 2) { throw new IllegalArgumentException("预测蓝球数量必须为2个"); } for (Integer ball : predictedBlueBalls) { if (ball == null || ball < 1 || ball > 16) { throw new IllegalArgumentException("预测蓝球号码必须在1-16范围内,实际:" + ball); } } if (predictedBlueBalls.stream().distinct().count() != predictedBlueBalls.size()) { throw new IllegalArgumentException("预测蓝球号码不能重复"); } // 验证上期红球 if (lastRedBalls == null || lastRedBalls.size() != 6) { throw new IllegalArgumentException("上期红球数量必须为6个"); } for (Integer ball : lastRedBalls) { if (ball == null || ball < 1 || ball > 33) { throw new IllegalArgumentException("上期红球号码必须在1-33范围内,实际:" + ball); } } if (lastRedBalls.stream().distinct().count() != lastRedBalls.size()) { throw new IllegalArgumentException("上期红球号码不能重复"); } // 验证上期蓝球 if (lastBlueBall == null || lastBlueBall < 1 || lastBlueBall > 16) { throw new IllegalArgumentException("上期蓝球号码必须在1-16范围内,实际:" + lastBlueBall); } log.debug("蓝球分析算法参数验证通过"); } /** * 从T6表获取指定红球对应的12个蓝球号码(线系数最大的12个) * @param masterBallNumber 主球号(红球) * @return 12个蓝球号码列表 */ private List getTop12FromT6(Integer masterBallNumber) { log.debug("从T6表查询主球{}的线系数数据", masterBallNumber); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("lineCoefficient") .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 List t6List = t6Mapper.selectList(queryWrapper); if (t6List.isEmpty()) { log.warn("T6表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } if (t6List.size() < 12) { log.warn("T6表数据不足12条,实际{}条", t6List.size()); return t6List.stream().map(T6::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前11个 List result = new ArrayList<>(); for (int i = 0; i < 11; i++) { result.add(t6List.get(i).getSlaveBallNumber()); } // 处理第12个位置:检查是否有相同的线系数 Double targetCoefficient = t6List.get(11).getLineCoefficient(); List candidatesFor12th = new ArrayList<>(); // 找出所有线系数等于第12个位置线系数的记录 for (int i = 11; i < t6List.size(); i++) { if (t6List.get(i).getLineCoefficient().equals(targetCoefficient)) { candidatesFor12th.add(t6List.get(i)); } else { break; // 线系数不同,停止查找 } } if (candidatesFor12th.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor12th.get(0).getSlaveBallNumber()); } else { // 有多个候选,通过blue_history_top_100表的点系数排序 log.debug("第12位有{}个相同线系数的候选:{}", candidatesFor12th.size(), candidatesFor12th.stream().map(T6::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromBlueHistoryTop100( candidatesFor12th.stream().map(T6::getSlaveBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } log.debug("T6表主球{}最终选择的{}个从球:{}", masterBallNumber, result.size(), result); return result; } /** * 从blue_history_top_100表中选择点系数最高的球号 * @param candidates 候选球号列表 * @return 点系数最高的球号 */ private Integer selectBestBallFromBlueHistoryTop100(List candidates) { log.debug("从blue_history_top_100表中选择最佳球号,候选:{}", candidates); if (candidates.isEmpty()) { throw new IllegalArgumentException("候选球号列表不能为空"); } if (candidates.size() == 1) { return candidates.get(0); } // 查询这些球号在blue_history_top_100表中的点系数 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.in("ballNumber", candidates) .orderByDesc("pointCoefficient") .orderByAsc("ballNumber"); // 点系数相同时按球号升序 List top100List = blueHistoryTop100Mapper.selectList(queryWrapper); if (top100List.isEmpty()) { log.warn("候选球号{}在blue_history_top_100表中未找到数据,返回最小球号", candidates); return Collections.min(candidates); } // 返回点系数最高的球号 Integer bestBall = top100List.get(0).getBallNumber(); log.debug("选择的最佳球号:{},点系数:{}", bestBall, top100List.get(0).getPointCoefficient()); 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 主球号(红球) * @param level 级别:H-高位,M-中位,L-低位 * @return 5个蓝球号码列表 */ private List getSimpleTop5FromT8ByLevel(Integer masterBallNumber, String level) { log.debug("从T8表查询主球{}的面系数数据,级别:{}", masterBallNumber, level); // 查询指定主球的所有数据,按面系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("faceCoefficient") .orderByAsc("slaveBallNumber"); // 面系数相同时按从球号升序 List t8List = t8Mapper.selectList(queryWrapper); if (t8List.isEmpty()) { log.warn("T8表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } List result = new ArrayList<>(); int size = t8List.size(); switch (level) { 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()); } // 处理第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 "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/blue_history_top多级回退比较 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); // 先按基本规则选出5个位置的球号 List baseResult = new ArrayList<>(); for (int i = startIndex; i <= endIndex; i++) { baseResult.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())); List topSelected = selectBlueBallsWithFallback( candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()), 1 ); 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; } } log.debug("T8表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result); return result; } /** * 从blue_history_top_100表获取点系数最高的前2个蓝球号码 * 如果第2个点系数不唯一,使用blue_history_top表进行二级筛选 * @return 前2个蓝球号码列表 */ private List getTop2FromBlueHistoryTop100() { log.debug("从blue_history_top_100表获取前2个蓝球号码"); // 查询所有数据,按点系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("pointCoefficient") .orderByAsc("ballNumber"); // 点系数相同时按球号升序 List top100List = blueHistoryTop100Mapper.selectList(queryWrapper); if (top100List.isEmpty()) { log.warn("blue_history_top_100表中没有数据"); return new ArrayList<>(); } if (top100List.size() == 1) { List result = Arrays.asList(top100List.get(0).getBallNumber()); log.debug("blue_history_top_100表只有1条数据:{}", result); return result; } List result = new ArrayList<>(); // 第1个直接取点系数最高的 result.add(top100List.get(0).getBallNumber()); log.debug("第1个蓝球号码:{},点系数:{}", top100List.get(0).getBallNumber(), top100List.get(0).getPointCoefficient()); // 处理第2个位置:检查是否有相同的点系数 Double targetCoefficient = top100List.get(1).getPointCoefficient(); List candidatesFor2nd = new ArrayList<>(); // 找出所有点系数等于第2个位置点系数的记录 for (int i = 1; i < top100List.size(); i++) { if (top100List.get(i).getPointCoefficient().equals(targetCoefficient)) { candidatesFor2nd.add(top100List.get(i)); } else { break; // 点系数不同,停止查找 } } if (candidatesFor2nd.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor2nd.get(0).getBallNumber()); log.debug("第2个蓝球号码:{},点系数:{}", candidatesFor2nd.get(0).getBallNumber(), candidatesFor2nd.get(0).getPointCoefficient()); } else { // 有多个候选,通过blue_history_top表的点系数排序 log.debug("第2位有{}个相同点系数的候选:{}", candidatesFor2nd.size(), candidatesFor2nd.stream().map(BlueHistoryTop100::getBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromBlueHistoryTop( candidatesFor2nd.stream().map(BlueHistoryTop100::getBallNumber).collect(Collectors.toList()) ); result.add(bestBall); log.debug("通过blue_history_top表筛选的第2个蓝球号码:{}", bestBall); } log.debug("从blue_history_top_100表最终获取到{}个蓝球号码:{}", result.size(), result); return result; } /** * 从T5表获取指定蓝球对应的5个蓝球号码(简化版本) * @param masterBallNumber 主球号(蓝球) * @param level 级别:H-高位,M-中位,L-低位 * @return 5个蓝球号码列表 */ private List getSimpleTop5FromT5ByLevel(Integer masterBallNumber, String level) { log.debug("从T5表查询主球{}的线系数数据,级别:{}", masterBallNumber, level); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("lineCoefficient") .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 List t5List = t5Mapper.selectList(queryWrapper); if (t5List.isEmpty()) { log.warn("T5表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } List result = new ArrayList<>(); int size = t5List.size(); switch (level) { case "H": // 高位:取线系数最大的5个 for (int i = 0; i < Math.min(5, size); i++) { result.add(t5List.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(t5List.get(i).getSlaveBallNumber()); } break; case "L": // 低位:取最后5个(线系数最小的) int startLow = Math.max(0, size - 5); for (int i = startLow; i < size; i++) { result.add(t5List.get(i).getSlaveBallNumber()); } break; } log.debug("T5表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result); return result; } /** * 统计数字出现频率,返回频率最高的前4个蓝球号码 * 如果频次相同的球号超过4个,使用多级筛选机制 */ private List getSimpleTop4ByFrequency(List allNumbers, List step2BlueT8Coefficients) { log.debug("统计{}个蓝球号码的出现频率", allNumbers.size()); // 统计频率 Map frequencyMap = new HashMap<>(); for (Integer number : allNumbers) { frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); } log.debug("蓝球号码频率统计:{}", frequencyMap); // 按频率分组 Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : frequencyMap.entrySet()) { Integer frequency = entry.getValue(); Integer ballNumber = entry.getKey(); frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); } log.debug("按频率分组:{}", frequencyGroups); List result = new ArrayList<>(); // 按频率从高到低处理 for (Map.Entry> group : frequencyGroups.entrySet()) { Integer frequency = group.getKey(); List balls = group.getValue(); // 对同频率的球号按数字升序排序 Collections.sort(balls); log.info("频率{}的蓝球号码:{}", frequency, balls); // 检查加入这组球号后是否会超过4个 if (result.size() + balls.size() <= 4) { // 不会超过4个,直接添加所有球号 result.addAll(balls); log.info("直接添加{}个蓝球号码,当前总数:{}", balls.size(), result.size()); } else { // 会超过4个:第一级直接用“第二步收集的T8面系数求和”进行筛选 int remainingSlots = 4 - result.size(); 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("通过第二步T8面系数和筛选完成,最终选择:{}", selectedBalls); break; // 已经达到4个,结束 } // 如果已经有4个,结束 if (result.size() >= 4) { break; } } log.info("频率统计最终结果(共{}个):{}", result.size(), result); // 打印详细的频率信息 for (int i = 0; i < result.size(); i++) { Integer ballNumber = result.get(i); Integer frequency = frequencyMap.get(ballNumber); log.info("第{}位:蓝球号码{},出现{}次", i + 1, ballNumber, frequency); } return result; } /** * 统计频率并获取前4个蓝球号码(带筛选过程说明) * @param allNumbers 所有蓝球号码 * @param step2BlueT8Coefficients 第二步T8面系数数据 * @return 包含结果和筛选过程说明的VO对象 */ private BlueBallAnalysisResultVO getSimpleTop4ByFrequencyWithProcess(List allNumbers, List step2BlueT8Coefficients) { log.debug("统计{}个蓝球号码的出现频率", allNumbers.size()); // 统计频率 Map frequencyMap = new HashMap<>(); for (Integer number : allNumbers) { frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); } log.debug("蓝球号码频率统计:{}", frequencyMap); // 按频率分组 Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : frequencyMap.entrySet()) { Integer frequency = entry.getValue(); Integer ballNumber = entry.getKey(); frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); } log.debug("按频率分组:{}", frequencyGroups); List result = new ArrayList<>(); List directlySelectedBalls = new ArrayList<>(); List multiLevelCandidates = new ArrayList<>(); List secondaryFilteredBalls = new ArrayList<>(); boolean needMultiLevelFiltering = false; StringBuilder processDescription = new StringBuilder(); String detailedStepsInfo = ""; // 构建参与筛选的球号频率分布描述 List allParticipatedBalls = new ArrayList<>(); Map> participatedFrequencyGroups = new LinkedHashMap<>(); // 按频率从高到低处理 for (Map.Entry> group : frequencyGroups.entrySet()) { Integer frequency = group.getKey(); List balls = group.getValue(); // 对同频率的球号按数字升序排序 Collections.sort(balls); log.info("频率{}的蓝球号码:{}", frequency, balls); // 检查加入这组球号后是否会超过4个 if (result.size() + balls.size() <= 4) { // 不会超过4个,直接添加所有球号 result.addAll(balls); directlySelectedBalls.addAll(balls); allParticipatedBalls.addAll(balls); log.info("直接添加{}个蓝球号码,当前总数:{}", balls.size(), result.size()); } else { // 会超过4个,需要多级筛选 needMultiLevelFiltering = true; multiLevelCandidates.addAll(balls); allParticipatedBalls.addAll(balls); int remainingSlots = 4 - result.size(); log.info("需要从{}个频率相同的蓝球号码中选择{}个,使用第二步T8面系数求和进行筛选", balls.size(), remainingSlots); // 进行多级筛选 BlueBallMultiLevelFilteringResult filteringResult = performBlueBallMultiLevelFilteringWithDetails(balls, remainingSlots, step2BlueT8Coefficients); result.addAll(filteringResult.getSelectedBalls()); secondaryFilteredBalls = new ArrayList<>(filteringResult.getSelectedBalls()); processDescription.append("筛选步骤:").append(filteringResult.getStepsDescription()); detailedStepsInfo = filteringResult.getDetailedStepsInfo(); // 已达到4个,结束 break; } // 如果已经有4个,结束 if (result.size() >= 4) { break; } } // 构建参与筛选的频率分布 for (Integer ball : allParticipatedBalls) { Integer frequency = frequencyMap.get(ball); participatedFrequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ball); } // 按频率从高到低排序 Map> sortedParticipatedFrequencyGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry> entry : participatedFrequencyGroups.entrySet()) { Integer frequency = entry.getKey(); List balls = new ArrayList<>(entry.getValue()); Collections.sort(balls); // 球号按升序排序 sortedParticipatedFrequencyGroups.put(frequency, balls); } // 构建详细的过程描述 StringBuilder detailedDescription = new StringBuilder(); // 1. 频率分布描述 - 只包含实际参与筛选的球号 detailedDescription.append("参与筛选的候选蓝球号码按频率分布为"); detailedDescription.append(formatBallNumbersWithFrequency(sortedParticipatedFrequencyGroups)); // 2. 筛选结果描述 if (needMultiLevelFiltering) { detailedDescription.append(";无法直接筛选出前4个,其中"); detailedDescription.append(formatBallNumbersComplete(directlySelectedBalls)); detailedDescription.append("直接入选,"); detailedDescription.append(formatBallNumbersComplete(multiLevelCandidates)); detailedDescription.append("需要进行二次筛选,最终筛选出"); detailedDescription.append(formatBallNumbersComplete(secondaryFilteredBalls)); detailedDescription.append(",组成前4个蓝球号码:"); detailedDescription.append(formatBallNumbersComplete(result)); detailedDescription.append("。"); detailedDescription.append(processDescription.toString()); if (!detailedStepsInfo.isEmpty()) { detailedDescription.append(" ").append(detailedStepsInfo); } } else { detailedDescription.append(";直接按频率筛选出前4个蓝球号码:"); detailedDescription.append(formatBallNumbersComplete(result)); detailedDescription.append("。筛选步骤:通过频率筛选确定所有球号,无需进行T8面系数和筛选、百期排位、历史排位。"); } return BlueBallAnalysisResultVO.builder() .result(result) .filteringProcess(detailedDescription.toString()) .build(); } /** * 从blue_history_top表中选择点系数最高的球号 * @param candidates 候选球号列表 * @return 点系数最高的球号 */ private Integer selectBestBallFromBlueHistoryTop(List candidates) { log.debug("从blue_history_top表中选择最佳球号,候选:{}", candidates); if (candidates.isEmpty()) { throw new IllegalArgumentException("候选球号列表不能为空"); } if (candidates.size() == 1) { return candidates.get(0); } // 查询这些球号在blue_history_top表中的点系数 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.in("ballNumber", candidates) .orderByDesc("pointCoefficient") .orderByAsc("ballNumber"); // 点系数相同时按球号升序 List historyTopList = blueHistoryTopMapper.selectList(queryWrapper); if (historyTopList.isEmpty()) { log.warn("候选球号{}在blue_history_top表中未找到数据,返回最小球号", candidates); return Collections.min(candidates); } // 返回点系数最高的球号 Integer bestBall = historyTopList.get(0).getBallNumber(); log.debug("选择的最佳球号:{},点系数:{}", bestBall, historyTopList.get(0).getPointCoefficient()); return bestBall; } /** * 四级筛选蓝球号码:T8面系数 -> blue_history_top_100排名 -> blue_history_top点系数 -> 随机选择 * @param candidateBalls 候选蓝球号码列表 * @param selectCount 需要选择的数量 * @return 选择的蓝球号码列表 */ private List selectBallsByMultiLevelFilteringForBlue4(List candidateBalls, int selectCount) { log.info("开始四级筛选蓝球号码,候选:{},需要选择:{}", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { log.info("候选蓝球号码数量不超过需要选择的数量,直接返回所有候选蓝球号码"); return new ArrayList<>(candidateBalls); } // 第一级:使用T8表的面系数总和进行筛选 List filteredByT8 = selectBallsByT8FaceCoefficientSumForBlue4(candidateBalls, selectCount); if (filteredByT8.size() == selectCount) { log.info("通过T8表面系数筛选成功,结果:{}", filteredByT8); return filteredByT8; } // 第二级:使用blue_history_top_100表的排名进行筛选 log.info("T8表筛选后仍有{}个蓝球号码,继续使用blue_history_top_100表筛选", filteredByT8.size()); List filteredByTop100 = selectBallsByBlueHistoryTop100RankingForBlue4(filteredByT8, selectCount); if (filteredByTop100.size() == selectCount) { log.info("通过blue_history_top_100表筛选成功,结果:{}", filteredByTop100); return filteredByTop100; } // 第三级:使用blue_history_top表的点系数进行筛选 log.info("blue_history_top_100表筛选后仍有{}个蓝球号码,继续使用blue_history_top表筛选", filteredByTop100.size()); List filteredByTop = selectBallsByBlueHistoryTopPointCoefficientForBlue4(filteredByTop100, selectCount); if (filteredByTop.size() == selectCount) { log.info("通过blue_history_top表筛选成功,结果:{}", filteredByTop); return filteredByTop; } // 第四级:随机选择 log.info("blue_history_top表筛选后仍有{}个蓝球号码,进行随机选择", filteredByTop.size()); List finalResult = selectBallsRandomlyForBlue4(filteredByTop, selectCount); log.info("随机选择完成,最终结果:{}", finalResult); return finalResult; } /** * 根据T8表的面系数总和选择蓝球号码(第一级筛选) * @param candidateBalls 候选蓝球号码列表 * @param selectCount 需要选择的数量 * @return 选择的蓝球号码列表 */ private List selectBallsByT8FaceCoefficientSumForBlue4(List candidateBalls, int selectCount) { log.info("开始根据T8表面系数总和选择蓝球号码,候选:{},需要选择:{}", candidateBalls, selectCount); if (candidateBalls.size() <= selectCount) { log.info("候选蓝球号码数量不超过需要选择的数量,直接返回所有候选蓝球号码"); return new ArrayList<>(candidateBalls); } // 计算每个蓝球号码作为主球时的面系数总和 Map faceCoefficientSumMap = new HashMap<>(); for (Integer ballNumber : candidateBalls) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", ballNumber); List t8List = t8Mapper.selectList(queryWrapper); double sum = t8List.stream() .mapToDouble(T8::getFaceCoefficient) .sum(); faceCoefficientSumMap.put(ballNumber, sum); log.debug("蓝球号码{}作为主球的面系数总和:{}", ballNumber, sum); } // 按面系数总和分组 Map> faceCoefficientGroups = new TreeMap<>(Collections.reverseOrder()); for (Map.Entry entry : faceCoefficientSumMap.entrySet()) { Double sum = entry.getValue(); Integer ballNumber = entry.getKey(); faceCoefficientGroups.computeIfAbsent(sum, k -> new ArrayList<>()).add(ballNumber); } log.info("T8表面系数总和分组:{}", faceCoefficientGroups); List result = new ArrayList<>(); // 按面系数总和从高到低处理 for (Map.Entry> group : faceCoefficientGroups.entrySet()) { Double faceSum = group.getKey(); List balls = group.getValue(); log.info("面系数总和{}的蓝球号码:{}", faceSum, 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; } } log.info("T8表面系数筛选结果:{}", result); return result; } /** * 根据blue_history_top_100表的点系数选择蓝球号码(第二级筛选) * @param candidateBalls 候选蓝球号码列表 * @param selectCount 需要选择的数量 * @return 选择的蓝球号码列表 */ private List selectBallsByBlueHistoryTop100RankingForBlue4(List candidateBalls, int selectCount) { log.info("使用blue_history_top_100表点系数筛选蓝球号码,候选:{},需要选择:{}", candidateBalls, selectCount); // 查询这些球号在blue_history_top_100表中的点系数 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.in("ballNumber", candidateBalls) .orderByDesc("pointCoefficient") .orderByAsc("ballNumber"); // 点系数相同时按球号升序 List top100List = blueHistoryTop100Mapper.selectList(queryWrapper); if (top100List.isEmpty()) { log.warn("候选球号{}在blue_history_top_100表中未找到数据,按球号升序返回", candidateBalls); Collections.sort(candidateBalls); return candidateBalls.subList(0, Math.min(selectCount, candidateBalls.size())); } // 按点系数分组 Map> pointCoefficientGroups = new TreeMap<>(Collections.reverseOrder()); for (BlueHistoryTop100 item : top100List) { Double pointCoefficient = item.getPointCoefficient(); Integer ballNumber = item.getBallNumber(); pointCoefficientGroups.computeIfAbsent(pointCoefficient, k -> new ArrayList<>()).add(ballNumber); } log.info("blue_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; } } // 处理不在blue_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("添加不在blue_history_top_100表中的{}个蓝球号码:{}", addCount, notInTop100.subList(0, addCount)); } log.info("blue_history_top_100表筛选结果:{}", result); return result; } /** * 根据blue_history_top表的点系数选择蓝球号码(第三级筛选) * @param candidateBalls 候选蓝球号码列表 * @param selectCount 需要选择的数量 * @return 选择的蓝球号码列表 */ private List selectBallsByBlueHistoryTopPointCoefficientForBlue4(List candidateBalls, int selectCount) { log.info("使用blue_history_top表点系数筛选蓝球号码,候选:{},需要选择:{}", candidateBalls, selectCount); // 查询这些球号在blue_history_top表中的点系数 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.in("ballNumber", candidateBalls) .orderByDesc("pointCoefficient") .orderByAsc("ballNumber"); // 点系数相同时按球号升序 List historyTopList = blueHistoryTopMapper.selectList(queryWrapper); if (historyTopList.isEmpty()) { log.warn("候选球号{}在blue_history_top表中未找到数据,按球号升序返回", candidateBalls); Collections.sort(candidateBalls); return candidateBalls.subList(0, Math.min(selectCount, candidateBalls.size())); } // 按点系数分组 Map> pointCoefficientGroups = new TreeMap<>(Collections.reverseOrder()); for (BlueHistoryTop item : historyTopList) { Double pointCoefficient = item.getPointCoefficient(); Integer ballNumber = item.getBallNumber(); pointCoefficientGroups.computeIfAbsent(pointCoefficient, k -> new ArrayList<>()).add(ballNumber); } log.info("blue_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; } } // 处理不在blue_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("添加不在blue_history_top表中的{}个蓝球号码:{}", addCount, notInHistoryTop.subList(0, addCount)); } log.info("blue_history_top表筛选结果:{}", result); return result; } /** * 随机选择蓝球号码(第四级筛选) * @param candidateBalls 候选蓝球号码列表 * @param selectCount 需要选择的数量 * @return 随机选择的蓝球号码列表 */ private List selectBallsRandomlyForBlue4(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; } /** * 首球命中率统计 * @param userId 用户ID * @return 首球命中率统计信息 */ public BallHitRateVO getFirstBallHitRate(Long userId) { log.info("开始统计用户{}的首球命中率", userId); // 查询用户的所有预测记录(除了"待开奖"状态的) QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("userId", userId) .ne("predictStatus", "待开奖"); List predictRecords = predictRecordMapper.selectList(queryWrapper); // 总预测次数 int totalCount = predictRecords.size(); // 统计首球命中次数 int hitCount = 0; for (PredictRecord record : predictRecords) { // 获取该预测记录对应的开奖信息 LotteryDraws draw = lotteryDrawsService.getByDrawId(record.getDrawId()); if (draw != null && record.getRedBall1() != null) { Integer predictedFirstBall = record.getRedBall1(); List drawnRedBalls = Arrays.asList( draw.getRedBall1(), draw.getRedBall2(), draw.getRedBall3(), draw.getRedBall4(), draw.getRedBall5(), draw.getRedBall6() ); // 比较预测的第一个红球是否在开奖的六个红球中 if (drawnRedBalls.contains(predictedFirstBall)) { hitCount++; } } } // 计算命中率 double hitRate = totalCount > 0 ? (double) hitCount / totalCount * 100 : 0; BallHitRateVO result = BallHitRateVO.builder() .hitCount(hitCount) .totalCount(totalCount) .hitRate(hitRate) .build(); log.info("用户{}的首球命中率统计结果:命中{}次,总计{}次,命中率{}%", userId, hitCount, totalCount, String.format("%.2f", hitRate)); return result; } /** * 蓝球命中率统计 * @param userId 用户ID * @return 蓝球命中率统计信息 */ public BallHitRateVO getBlueBallHitRate(Long userId) { log.info("开始统计用户{}的蓝球命中率", userId); // 查询用户的所有预测记录(除了"待开奖"状态的) QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("userId", userId) .ne("predictStatus", "待开奖"); List predictRecords = predictRecordMapper.selectList(queryWrapper); // 总预测次数 int totalCount = predictRecords.size(); // 统计蓝球命中次数 int hitCount = 0; for (PredictRecord record : predictRecords) { // 获取该预测记录对应的开奖信息 LotteryDraws draw = lotteryDrawsService.getByDrawId(record.getDrawId()); if (draw != null && record.getBlueBall() != null) { // 比较预测的蓝球与开奖的蓝球是否相等 if (record.getBlueBall().equals(draw.getBlueBall())) { hitCount++; } } } // 计算命中率 double hitRate = totalCount > 0 ? (double) hitCount / totalCount * 100 : 0; BallHitRateVO result = BallHitRateVO.builder() .hitCount(hitCount) .totalCount(totalCount) .hitRate(hitRate) .build(); log.info("用户{}的蓝球命中率统计结果:命中{}次,总计{}次,命中率{}%", userId, hitCount, totalCount, String.format("%.2f", hitRate)); return result; } /** * 奖金统计 * @param userId 用户ID * @param predictId 预测记录ID,如果为null则统计所有记录 * @return 奖金统计信息 */ public PrizeEstimateVO getPrizeStatistics(Long userId, Long predictId) { log.info("开始为用户{}进行奖金统计,predictId={}", userId, predictId); // 查询条件 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("userId", userId); // 排除待开奖状态 queryWrapper.ne("predictStatus", "待开奖"); // 如果指定了预测记录ID,则只查询该记录 if (predictId != null) { queryWrapper.eq("id", predictId); } // 查询用户的预测记录 List records = predictRecordMapper.selectList(queryWrapper); // 按中奖等级分组统计 Map countByPrizeLevel = new HashMap<>(); Map totalBonusByPrizeLevel = new HashMap<>(); // 初始化所有等级 String[] prizeLevels = {"一等奖", "二等奖", "三等奖", "四等奖", "五等奖", "六等奖", "未中奖"}; for (String level : prizeLevels) { countByPrizeLevel.put(level, 0); totalBonusByPrizeLevel.put(level, BigDecimal.ZERO); } // 统计各等级数量和奖金 for (PredictRecord record : records) { String prizeLevel = record.getPredictResult(); if (prizeLevel == null) { prizeLevel = "未中奖"; } // 更新计数 countByPrizeLevel.put(prizeLevel, countByPrizeLevel.getOrDefault(prizeLevel, 0) + 1); // 累计奖金 if (record.getBonus() != null) { BigDecimal bonus = new BigDecimal(record.getBonus().toString()); totalBonusByPrizeLevel.put(prizeLevel, totalBonusByPrizeLevel.getOrDefault(prizeLevel, BigDecimal.ZERO).add(bonus)); } } // 构建返回结果 List prizeDetails = new ArrayList<>(); // 按顺序添加各等级的统计结果(从高到低) for (String level : prizeLevels) { // 跳过没有记录的等级 if (countByPrizeLevel.get(level) <= 0) { continue; } int count = countByPrizeLevel.get(level); BigDecimal totalBonus = totalBonusByPrizeLevel.get(level); BigDecimal singlePrize = BigDecimal.ZERO; if (count > 0) { singlePrize = totalBonus.divide(new BigDecimal(count), 2, RoundingMode.HALF_UP); } prizeDetails.add(PrizeEstimateVO.PrizeDetailItem.builder() .prizeLevel(level) .winningCount(count) .singlePrize(singlePrize) .subtotal(totalBonus) .build()); } // 计算总奖金 BigDecimal totalPrize = prizeDetails.stream() .map(PrizeEstimateVO.PrizeDetailItem::getSubtotal) .reduce(BigDecimal.ZERO, BigDecimal::add); PrizeEstimateVO result = PrizeEstimateVO.builder() .totalPrize(totalPrize) .prizeDetails(prizeDetails) .build(); log.info("用户{}的奖金统计结果:总奖金{},各等级明细:{}", userId, totalPrize, prizeDetails); return result; } /** * 红球命中率统计 * @param userId 用户ID * @return 红球命中率统计信息 */ public RedBallHitRateVO getRedBallHitRate(Long userId) { log.info("开始统计用户{}的红球命中率", userId); // 查询用户的所有预测记录(除了"待开奖"状态的) QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("userId", userId) .ne("predictStatus", "待开奖"); List predictRecords = predictRecordMapper.selectList(queryWrapper); int totalHitCount = 0; int totalRecords = predictRecords.size(); for (PredictRecord record : predictRecords) { // 获取该预测记录对应的开奖信息 LotteryDraws draw = lotteryDrawsService.getByDrawId(record.getDrawId()); if (draw != null) { List predictedRedBalls = Arrays.asList( record.getRedBall1(), record.getRedBall2(), record.getRedBall3(), record.getRedBall4(), record.getRedBall5(), record.getRedBall6() ); List drawnRedBalls = Arrays.asList( draw.getRedBall1(), draw.getRedBall2(), draw.getRedBall3(), draw.getRedBall4(), draw.getRedBall5(), draw.getRedBall6() ); // 计算当前记录命中的红球数 for (Integer predictedBall : predictedRedBalls) { if (predictedBall != null && drawnRedBalls.contains(predictedBall)) { totalHitCount++; } } } } int totalPredictedCount = totalRecords * 6; double hitRate = (totalPredictedCount > 0) ? ((double) totalHitCount / totalPredictedCount) * 100 : 0; RedBallHitRateVO result = RedBallHitRateVO.builder() .totalHitCount(totalHitCount) .totalPredictedCount(totalPredictedCount) .hitRate(hitRate) .build(); log.info("用户{}的红球命中率统计结果:命中总数{},预测总数{},命中率{}%", userId, totalHitCount, totalPredictedCount, String.format("%.2f", hitRate)); return result; } /** * 从T4表获取指定主球的26个从球号,取线系数最大值向下26个数字 * @param masterBallNumber 主球号 */ private List getTop26FromT4(Integer masterBallNumber) { log.debug("从T4表查询主球{}的线系数数据,取前26个", masterBallNumber); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("lineCoefficient") .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 List t4List = t4Mapper.selectList(queryWrapper); if (t4List.isEmpty()) { log.warn("T4表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } if (t4List.size() < 26) { log.warn("T4表数据不足26条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前25个 List result = new ArrayList<>(); for (int i = 0; i < 25; i++) { result.add(t4List.get(i).getSlaveBallNumber()); } // 处理第26个位置:检查是否有相同的线系数 Double targetCoefficient = t4List.get(25).getLineCoefficient(); List candidatesFor26th = new ArrayList<>(); // 找出所有线系数等于第26个位置线系数的记录 for (int i = 25; i < t4List.size(); i++) { if (t4List.get(i).getLineCoefficient().equals(targetCoefficient)) { candidatesFor26th.add(t4List.get(i)); } else { break; // 线系数不同,停止查找 } } if (candidatesFor26th.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor26th.get(0).getSlaveBallNumber()); } else { // 有多个候选,通过history_top_100表的点系数排序 log.debug("第26位有{}个相同线系数的候选:{}", candidatesFor26th.size(), candidatesFor26th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( candidatesFor26th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } log.debug("T4表主球{}最终选择的26个从球:{}", masterBallNumber, result); return result; } /** * 从T5表获取指定主球的12个从球号,取线系数最大值向下12个数字 * @param masterBallNumber 主球号 */ private List getTop12FromT5(Integer masterBallNumber) { log.debug("从T5表查询主球{}的线系数数据,取前12个(含毛边处理)", masterBallNumber); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("lineCoefficient") .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 List t5List = t5Mapper.selectList(queryWrapper); if (t5List.isEmpty()) { log.warn("T5表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } if (t5List.size() < 12) { log.warn("T5表数据不足12条,实际{}条", t5List.size()); return t5List.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList()); } // 完全复刻补齐策略: // 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++; } // 先加入所有大于阈值的球号 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 { // 在等于组中先用 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); } } // 若仍不足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); return result; } /** * 从T4表获取指定主球的17个从球号,取线系数最大值向下17个数字 * @param masterBallNumber 主球号 */ private List getTop17FromT4(Integer masterBallNumber) { log.debug("从T4表查询主球{}的线系数数据,取前17个", masterBallNumber); // 查询指定主球的所有数据,按线系数降序排列 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("masterBallNumber", masterBallNumber) .orderByDesc("lineCoefficient") .orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序 List t4List = t4Mapper.selectList(queryWrapper); if (t4List.isEmpty()) { log.warn("T4表中主球{}没有数据", masterBallNumber); return new ArrayList<>(); } if (t4List.size() < 17) { log.warn("T4表数据不足17条,实际{}条", t4List.size()); return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()); } // 获取前16个 List result = new ArrayList<>(); for (int i = 0; i < 16; i++) { result.add(t4List.get(i).getSlaveBallNumber()); } // 处理第17个位置:检查是否有相同的线系数 Double targetCoefficient = t4List.get(16).getLineCoefficient(); List candidatesFor17th = new ArrayList<>(); // 找出所有线系数等于第17个位置线系数的记录 for (int i = 16; i < t4List.size(); i++) { if (t4List.get(i).getLineCoefficient().equals(targetCoefficient)) { candidatesFor17th.add(t4List.get(i)); } else { break; // 线系数不同,停止查找 } } if (candidatesFor17th.size() == 1) { // 只有一个候选,直接添加 result.add(candidatesFor17th.get(0).getSlaveBallNumber()); } else { // 有多个候选,通过history_top_100表的点系数排序 log.debug("第17位有{}个相同线系数的候选:{}", candidatesFor17th.size(), candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); Integer bestBall = selectBestBallFromHistoryTop100( candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) ); result.add(bestBall); } log.debug("T4表主球{}最终选择的17个从球:{}", masterBallNumber, result); return result; } }