Files
cpzs-backend/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java
2025-11-04 17:18:21 +08:00

6010 lines
275 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<BallWithCoefficient> getT7CoefficientsFor(Integer masterBallNumber, List<Integer> slaveBallNumbers) {
if (slaveBallNumbers == null || slaveBallNumbers.isEmpty()) {
return new ArrayList<>();
}
QueryWrapper<T7> qw = new QueryWrapper<>();
qw.eq("masterBallNumber", masterBallNumber)
.in("slaveBallNumber", slaveBallNumbers);
List<T7> rows = t7Mapper.selectList(qw);
// 用球号->系数映射,避免顺序问题
Map<Integer, Double> num2Coeff = rows.stream()
.collect(Collectors.toMap(T7::getSlaveBallNumber, T7::getFaceCoefficient, (a,b)->a));
List<BallWithCoefficient> 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<BallWithCoefficient> getT8CoefficientsFor(Integer masterBallNumber, List<Integer> slaveBallNumbers) {
if (slaveBallNumbers == null || slaveBallNumbers.isEmpty()) {
return new ArrayList<>();
}
QueryWrapper<T8> qw = new QueryWrapper<>();
qw.eq("masterBallNumber", masterBallNumber)
.in("slaveBallNumber", slaveBallNumbers);
List<T8> rows = t8Mapper.selectList(qw);
Map<Integer, Double> num2Coeff = rows.stream()
.collect(Collectors.toMap(T8::getSlaveBallNumber, T8::getFaceCoefficient, (a,b)->a));
List<BallWithCoefficient> 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<Integer> analyzeBalls(String level, List<Integer> 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<Integer> allNumbers = new ArrayList<>();
// 第一步:记录球号和系数的对应关系
List<BallWithCoefficient> 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<BallWithCoefficient> ballsWithCoeffs = getTop17FromT3WithCoefficients(redBall, level);
ballsWithCoefficients.addAll(ballsWithCoeffs);
List<Integer> 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<Integer> top3Numbers = getTop3FromHistoryTop();
allNumbers.addAll(top3Numbers);
log.info("从history_top获取到{}个数字:{}", top3Numbers.size(), top3Numbers);
// 第三步用蓝球从t4表获取17个数字
log.info("第三步:用蓝球{}从t4表获取17个数字使用{}级别算法", blueBall, level);
List<Integer> blueNumbers = getTop17FromT4(blueBall, level);
allNumbers.addAll(blueNumbers);
log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers);
log.info("总共收集到{}个数字", allNumbers.size());
// 第四步统计频率并获取前11个
List<Integer> 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<Integer> 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<Integer> allNumbers = new ArrayList<>();
// 第一步:记录球号和系数的对应关系
List<BallWithCoefficient> 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<BallWithCoefficient> ballsWithCoeffs = getTop17FromT3WithCoefficients(redBall, level);
ballsWithCoefficients.addAll(ballsWithCoeffs);
List<Integer> 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<Integer> top3Numbers = getTop3FromHistoryTop();
allNumbers.addAll(top3Numbers);
log.info("从history_top获取到{}个数字:{}", top3Numbers.size(), top3Numbers);
// 第三步用蓝球从t4表获取17个数字
log.info("第三步:用蓝球{}从t4表获取17个数字使用{}级别算法", blueBall, level);
List<Integer> 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<BallWithCoefficient> getTop17FromT3WithCoefficients(Integer masterBallNumber, String level) {
log.debug("从T3表查询主球{}的线系数数据(含系数),级别:{}", masterBallNumber, level);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T3> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T3> t3List = t3Mapper.selectList(queryWrapper);
if (t3List.isEmpty()) {
log.warn("T3表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<BallWithCoefficient> 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<Integer> getTop17FromT3(Integer masterBallNumber, String level) {
log.debug("从T3表查询主球{}的线系数数据,级别:{}", masterBallNumber, level);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T3> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T3> t3List = t3Mapper.selectList(queryWrapper);
if (t3List.isEmpty()) {
log.warn("T3表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<Integer> 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<Integer> getTop17FromT4(Integer masterBallNumber, String level) {
log.debug("从T4表查询主球{}的线系数数据,级别:{}", masterBallNumber, level);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T4> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T4> t4List = t4Mapper.selectList(queryWrapper);
if (t4List.isEmpty()) {
log.warn("T4表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<Integer> 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<Integer> getHighLevelBalls(List<T3> t3List) {
if (t3List.size() < 17) {
log.warn("T3表数据不足17条实际{}条", t3List.size());
return t3List.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList());
}
// 获取前16个
List<Integer> result = new ArrayList<>();
for (int i = 0; i < 16; i++) {
result.add(t3List.get(i).getSlaveBallNumber());
}
// 处理第17个位置检查是否有相同的线系数
Double targetCoefficient = t3List.get(16).getLineCoefficient();
List<T3> 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<BallWithCoefficient> getHighLevelBallsWithCoefficients(List<T3> 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<BallWithCoefficient> 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<T3> 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<Integer> getMiddleLevelBalls(List<T3> 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<Integer> 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<Integer> handleT3BoundaryConflicts(List<T3> t3List, List<Integer> candidates, int startIndex, int endIndex) {
List<Integer> result = new ArrayList<>(candidates);
if (result.size() >= 17) {
// 检查第1个位置最大线系数的冲突
Double firstCoefficient = t3List.get(startIndex).getLineCoefficient();
List<T3> 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<T3> 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<BallWithCoefficient> getMiddleLevelBallsWithCoefficients(List<T3> 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<BallWithCoefficient> 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<T3> 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<T3> 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<Integer> getLowLevelBalls(List<T3> 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<Integer> 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<T3> 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<BallWithCoefficient> getLowLevelBallsWithCoefficients(List<T3> 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<BallWithCoefficient> 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<T3> 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<Integer> getHighLevelBallsFromT4(List<T4> t4List) {
if (t4List.size() < 17) {
log.warn("T4表数据不足17条实际{}条", t4List.size());
return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList());
}
// 获取前16个
List<Integer> result = new ArrayList<>();
for (int i = 0; i < 16; i++) {
result.add(t4List.get(i).getSlaveBallNumber());
}
// 处理第17个位置检查是否有相同的线系数
Double targetCoefficient = t4List.get(16).getLineCoefficient();
List<T4> 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<Integer> getMiddleLevelBallsFromT4(List<T4> 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<Integer> 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<Integer> handleT4BoundaryConflicts(List<T4> t4List, List<Integer> candidates,
int startIndex, int endIndex) {
List<Integer> result = new ArrayList<>(candidates);
if (result.size() >= 17) {
// 检查第1个位置最大线系数的冲突
Double firstCoefficient = t4List.get(startIndex).getLineCoefficient();
List<T4> 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<T4> 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<Integer> getLowLevelBallsFromT4(List<T4> 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<Integer> 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<T4> 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<Integer> handleT4BoundaryConflictsFor10(List<T4> t4List, List<Integer> candidates,
int startIndex, int endIndex) {
List<Integer> result = new ArrayList<>(candidates);
if (result.size() >= 10) {
// 检查第1个位置最大线系数的冲突
Double firstCoefficient = t4List.get(startIndex).getLineCoefficient();
List<T4> 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<T4> 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<Integer> getTop3FromHistoryTop() {
log.debug("从history_top表获取前3个球号");
// 查询前3个排行的数据
QueryWrapper<HistoryTop> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("no").last("LIMIT 3");
List<HistoryTop> 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<Integer> result = new ArrayList<>();
// 处理每个位置,检查是否有相同点系数的情况
for (int i = 0; i < 3; i++) {
HistoryTop current = topList.get(i);
// 查找所有具有相同排行的记录
QueryWrapper<HistoryTop> sameRankQuery = new QueryWrapper<>();
sameRankQuery.eq("no", current.getNo());
List<HistoryTop> 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<Integer> 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<HistoryTop100> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidates)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<HistoryTop100> 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<Integer> candidates) {
if (candidates == null || candidates.isEmpty()) {
throw new IllegalArgumentException("候选球号列表不能为空");
}
if (candidates.size() == 1) {
return candidates.get(0);
}
// 优先history_top_100
QueryWrapper<HistoryTop100> top100Query = new QueryWrapper<>();
top100Query.in("ballNumber", candidates)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<HistoryTop100> top100List = historyTop100Mapper.selectList(top100Query);
if (top100List != null && !top100List.isEmpty()) {
return top100List.get(0).getBallNumber();
}
// 其次history_top
QueryWrapper<HistoryTop> topQuery = new QueryWrapper<>();
topQuery.in("ballNumber", candidates)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<HistoryTop> 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<Integer> getTop11ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> ballsWithCoefficients) {
log.debug("统计{}个数字的出现频率", allNumbers.size());
// 统计频率
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (Integer number : allNumbers) {
frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1);
}
log.debug("数字频率统计:{}", frequencyMap);
// 打印所有球号出现频率的详细信息
log.info("=== 所有球号出现频率统计 ===");
List<Map.Entry<Integer, Integer>> sortedEntries = frequencyMap.entrySet().stream()
.sorted(Map.Entry.<Integer, Integer>comparingByValue().reversed()
.thenComparing(Map.Entry.comparingByKey()))
.collect(Collectors.toList());
for (int i = 0; i < sortedEntries.size(); i++) {
Map.Entry<Integer, Integer> entry = sortedEntries.get(i);
log.info("第{}位:数字{},出现{}次", i + 1, entry.getKey(), entry.getValue());
}
log.info("=== 频率统计结束 ===");
// 按频率分组
Map<Integer, List<Integer>> frequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
Integer frequency = entry.getValue();
Integer ballNumber = entry.getKey();
frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber);
}
log.debug("按频率分组:{}", frequencyGroups);
List<Integer> result = new ArrayList<>();
// 按频率从高到低处理
for (Map.Entry<Integer, List<Integer>> group : frequencyGroups.entrySet()) {
Integer frequency = group.getKey();
List<Integer> 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<Integer> 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<Integer> allNumbers, List<BallWithCoefficient> ballsWithCoefficients) {
log.debug("统计{}个数字的出现频率", allNumbers.size());
StringBuilder processDescription = new StringBuilder();
// 统计频率
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (Integer number : allNumbers) {
frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1);
}
log.debug("数字频率统计:{}", frequencyMap);
// 按频率分组
Map<Integer, List<Integer>> frequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
Integer frequency = entry.getValue();
Integer ballNumber = entry.getKey();
frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber);
}
List<Integer> result = new ArrayList<>();
boolean needMultiLevelFiltering = false;
List<Integer> multiLevelCandidates = new ArrayList<>();
List<Integer> directlySelectedBalls = new ArrayList<>();
List<Integer> secondaryFilteredBalls = new ArrayList<>();
// 统计所有参与筛选的高频球号(用于显示)
Map<Integer, List<Integer>> allCandidateFrequencyGroups = new LinkedHashMap<>();
int highestFrequency = frequencyGroups.keySet().iterator().next();
// 收集所有可能参与筛选的球号(不只是最高频率,而是前几个高频率组)
int frequencyThreshold = Math.max(1, highestFrequency - 2); // 显示最高频率、次高频率、第三高频率等
for (Map.Entry<Integer, List<Integer>> group : frequencyGroups.entrySet()) {
Integer frequency = group.getKey();
List<Integer> balls = group.getValue();
Collections.sort(balls);
// 只收集较高频率的球号用于显示
if (frequency >= frequencyThreshold) {
allCandidateFrequencyGroups.put(frequency, new ArrayList<>(balls));
}
}
// 按频率从高到低处理,进行实际筛选
for (Map.Entry<Integer, List<Integer>> group : frequencyGroups.entrySet()) {
List<Integer> 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<Integer> selectBallsByMultiLevelFiltering(List<Integer> candidateBalls, int selectCount, List<BallWithCoefficient> ballsWithCoefficients) {
log.debug("开始多层筛选,候选球号:{},需要选择:{}个", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
return new ArrayList<>(candidateBalls);
}
List<Integer> currentCandidates = new ArrayList<>(candidateBalls);
// 第一层ballNumbersWithCoefficients系数和筛选
log.debug("=== 第一层ballNumbersWithCoefficients系数和筛选 ===");
List<Integer> 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<Integer> 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<Integer> 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<Integer> randomSelected = selectBallsRandomly(currentCandidates, selectCount);
log.debug("随机筛选完成,选择:{}", randomSelected);
return randomSelected;
}
/**
* 多层筛选包含过程说明ballNumbersWithCoefficients系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择
*/
private List<Integer> selectBallsByMultiLevelFilteringWithProcess(List<Integer> candidateBalls, int selectCount,
List<BallWithCoefficient> ballsWithCoefficients, StringBuilder processDescription) {
log.debug("开始多层筛选(含过程说明),候选球号:{},需要选择:{}个", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
return new ArrayList<>(candidateBalls);
}
List<Integer> currentCandidates = new ArrayList<>(candidateBalls);
List<String> filteringSteps = new ArrayList<>();
// 第一层ballNumbersWithCoefficients系数和筛选
log.debug("=== 第一层ballNumbersWithCoefficients系数和筛选 ===");
List<Integer> 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<Integer> 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<Integer> 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<Integer> randomSelected = selectBallsRandomly(currentCandidates, selectCount);
log.debug("随机筛选完成,选择:{}", randomSelected);
if (currentCandidates.size() > selectCount) {
filteringSteps.add("随机选择");
}
return randomSelected;
}
/**
* 筛选详情结果内部类
*/
private static class FilteringDetailResult {
private List<Integer> filteredBalls;
private String detailInfo;
public FilteringDetailResult(List<Integer> filteredBalls, String detailInfo) {
this.filteredBalls = filteredBalls;
this.detailInfo = detailInfo;
}
public List<Integer> getFilteredBalls() {
return filteredBalls;
}
public String getDetailInfo() {
return detailInfo;
}
}
/**
* 多层筛选结果内部类
*/
private static class MultiLevelFilteringResult {
private List<Integer> selectedBalls;
private String stepsDescription;
private String detailedStepsInfo;
public MultiLevelFilteringResult(List<Integer> selectedBalls, String stepsDescription) {
this.selectedBalls = selectedBalls;
this.stepsDescription = stepsDescription;
this.detailedStepsInfo = "";
}
public MultiLevelFilteringResult(List<Integer> selectedBalls, String stepsDescription, String detailedStepsInfo) {
this.selectedBalls = selectedBalls;
this.stepsDescription = stepsDescription;
this.detailedStepsInfo = detailedStepsInfo;
}
public List<Integer> getSelectedBalls() {
return selectedBalls;
}
public String getStepsDescription() {
return stepsDescription;
}
public String getDetailedStepsInfo() {
return detailedStepsInfo;
}
}
/**
* 跟随球号多级筛选结果内部类
*/
private static class FollowBallMultiLevelFilteringResult {
private List<Integer> selectedBalls;
private String stepsDescription;
private String detailedStepsInfo;
public FollowBallMultiLevelFilteringResult(List<Integer> selectedBalls, String stepsDescription) {
this.selectedBalls = selectedBalls;
this.stepsDescription = stepsDescription;
this.detailedStepsInfo = "";
}
public FollowBallMultiLevelFilteringResult(List<Integer> selectedBalls, String stepsDescription, String detailedStepsInfo) {
this.selectedBalls = selectedBalls;
this.stepsDescription = stepsDescription;
this.detailedStepsInfo = detailedStepsInfo;
}
public List<Integer> 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<Integer> candidateBalls, int selectCount, List<BallWithCoefficient> step1BallsWithCoefficients) {
List<String> completedSteps = new ArrayList<>();
List<String> skippedSteps = new ArrayList<>();
// 构建 step1 球号->面系数 映射找不到则为0
Map<Integer, Double> num2Coeff = new HashMap<>();
if (step1BallsWithCoefficients != null) {
for (BallWithCoefficient bwc : step1BallsWithCoefficients) {
num2Coeff.put(bwc.getBallNumber(), bwc.getCoefficient());
}
}
// 先按面系数降序,再按球号升序
List<Integer> 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<Double, List<Integer>> 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<Double, List<Integer>> entry : t7Groups.entrySet()) {
List<Integer> 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<Integer> 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<Integer> higherThanThreshold = new ArrayList<>();
List<Integer> 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<Integer> 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<Integer> selectedBalls;
private String detailedInfo;
public FollowBallTop100HistoryFilteringResult(List<Integer> selectedBalls, String detailedInfo) {
this.selectedBalls = selectedBalls;
this.detailedInfo = detailedInfo;
}
public List<Integer> getSelectedBalls() {
return selectedBalls;
}
public String getDetailedInfo() {
return detailedInfo;
}
}
/**
* 蓝球多级筛选结果内部类
*/
private static class BlueBallMultiLevelFilteringResult {
private List<Integer> selectedBalls;
private String stepsDescription;
private String detailedStepsInfo;
public BlueBallMultiLevelFilteringResult(List<Integer> selectedBalls, String stepsDescription) {
this.selectedBalls = selectedBalls;
this.stepsDescription = stepsDescription;
this.detailedStepsInfo = "";
}
public BlueBallMultiLevelFilteringResult(List<Integer> selectedBalls, String stepsDescription, String detailedStepsInfo) {
this.selectedBalls = selectedBalls;
this.stepsDescription = stepsDescription;
this.detailedStepsInfo = detailedStepsInfo;
}
public List<Integer> getSelectedBalls() {
return selectedBalls;
}
public String getStepsDescription() {
return stepsDescription;
}
public String getDetailedStepsInfo() {
return detailedStepsInfo;
}
}
/**
* 通过百期排位和历史排位进行筛选(带详细信息)
*/
private FollowBallTop100HistoryFilteringResult selectByTop100ThenHistoryTopWithDetails(List<Integer> candidateBalls, int needCount) {
if (candidateBalls == null || candidateBalls.isEmpty() || needCount <= 0) {
return new FollowBallTop100HistoryFilteringResult(new ArrayList<>(), "");
}
StringBuilder detailedInfo = new StringBuilder();
// 在同T7系数的候选中先用100期逐组加入
QueryWrapper<HistoryTop100> query = new QueryWrapper<>();
query.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<HistoryTop100> top100List = historyTop100Mapper.selectList(query);
List<Integer> selectedByTop100 = new ArrayList<>();
List<Integer> undecidedGroup = new ArrayList<>();
StringBuilder top100Details = new StringBuilder();
if (top100List != null && !top100List.isEmpty()) {
// 记录所有球号的百期排位详情
Map<Double, List<Integer>> top100Groups = new TreeMap<>(Collections.reverseOrder());
for (HistoryTop100 item : top100List) {
top100Groups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()).add(item.getBallNumber());
}
for (Map.Entry<Double, List<Integer>> entry : top100Groups.entrySet()) {
List<Integer> 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<Integer> notInTop100 = new ArrayList<>(candidateBalls);
List<Integer> 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<Double, List<Integer>> groups = new LinkedHashMap<>();
for (HistoryTop100 item : top100List) {
groups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>())
.add(item.getBallNumber());
}
for (Map.Entry<Double, List<Integer>> entry : groups.entrySet()) {
List<Integer> 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<Integer> forHistoryTop = !undecidedGroup.isEmpty() ? new ArrayList<>(undecidedGroup) :
(top100List == null || top100List.isEmpty() ? new ArrayList<>(candidateBalls) : new ArrayList<>());
List<Integer> selectedByHistoryTop = new ArrayList<>();
StringBuilder historyDetails = new StringBuilder();
if (!forHistoryTop.isEmpty()) {
QueryWrapper<HistoryTop> historyQuery = new QueryWrapper<>();
historyQuery.in("ballNumber", forHistoryTop)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<HistoryTop> historyTopList = historyTopMapper.selectList(historyQuery);
if (historyTopList != null && !historyTopList.isEmpty()) {
// 记录所有球号的历史排位详情
Map<Double, List<Integer>> historyGroups = new TreeMap<>(Collections.reverseOrder());
for (HistoryTop item : historyTopList) {
historyGroups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()).add(item.getBallNumber());
}
for (Map.Entry<Double, List<Integer>> entry : historyGroups.entrySet()) {
List<Integer> 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<Integer> notInHistory = new ArrayList<>(forHistoryTop);
List<Integer> 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<Integer> 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<String> completedSteps, List<String> 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<Integer> candidateBalls, int selectCount, List<BallWithCoefficient> step2BlueT8Coefficients) {
List<String> completedSteps = new ArrayList<>();
List<String> skippedSteps = new ArrayList<>();
// 统计同频候选在step2BlueT8Coefficients中的面系数和找不到视为0
Map<Integer, Double> sumMap = new HashMap<>();
StringBuilder t8Details = new StringBuilder();
Map<Double, List<Integer>> 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<Double, List<Integer>> entry : t8Groups.entrySet()) {
List<Integer> 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<Integer> 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<Integer> 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<String> completedSteps, List<String> 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<Integer> ballNumbers) {
if (ballNumbers == null || ballNumbers.isEmpty()) {
return "";
}
List<Integer> sortedBalls = new ArrayList<>(ballNumbers);
Collections.sort(sortedBalls);
if (sortedBalls.size() <= 10) {
return sortedBalls.toString().replace("[", "").replace("]", "");
} else {
// 如果球号太多,只显示前几个和后几个
List<Integer> 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<Integer> ballNumbers) {
if (ballNumbers == null || ballNumbers.isEmpty()) {
return "";
}
List<Integer> sortedBalls = new ArrayList<>(ballNumbers);
Collections.sort(sortedBalls);
return sortedBalls.toString().replace("[", "").replace("]", "");
}
/**
* 格式化带频率的球号列表为字符串
* @param frequencyGroups 按频率分组的球号key为频率value为球号列表
*/
private String formatBallNumbersWithFrequency(Map<Integer, List<Integer>> frequencyGroups) {
if (frequencyGroups == null || frequencyGroups.isEmpty()) {
return "";
}
StringBuilder result = new StringBuilder();
List<String> parts = new ArrayList<>();
// 按频率从高到低排序
List<Map.Entry<Integer, List<Integer>>> sortedEntries = frequencyGroups.entrySet().stream()
.sorted(Map.Entry.<Integer, List<Integer>>comparingByKey().reversed())
.collect(Collectors.toList());
for (Map.Entry<Integer, List<Integer>> entry : sortedEntries) {
Integer frequency = entry.getKey();
List<Integer> 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<Integer> candidateBalls,
int selectCount, List<BallWithCoefficient> ballsWithCoefficients) {
log.debug("开始详细多层筛选,候选球号:{},需要选择:{}个", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
return new MultiLevelFilteringResult(new ArrayList<>(candidateBalls), "直接选择,无需进一步筛选。");
}
List<Integer> currentCandidates = new ArrayList<>(candidateBalls);
List<String> completedSteps = new ArrayList<>();
List<String> skippedSteps = new ArrayList<>();
StringBuilder detailedInfo = new StringBuilder();
// 第一层ballNumbersWithCoefficients系数和筛选
log.debug("=== 第一层T3系数和筛选 ===");
FilteringDetailResult t3Result = selectBallsByBallNumbersWithCoefficientsWithDetails(currentCandidates, selectCount, ballsWithCoefficients);
List<Integer> 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<Integer> 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<Integer> 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<Integer> 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<String> completedSteps, List<String> 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<Integer> selectBallsByBallNumbersWithCoefficients(List<Integer> candidateBalls, int selectCount, List<BallWithCoefficient> ballsWithCoefficients) {
log.debug("使用ballNumbersWithCoefficients系数和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount);
if (candidateBalls.size() <= selectCount) {
return new ArrayList<>(candidateBalls);
}
// 计算每个球号的系数和
Map<Integer, Double> ballCoefficientSum = new HashMap<>();
for (Integer ballNumber : candidateBalls) {
double coefficientSum = calculateBallNumbersWithCoefficientsSum(ballNumber, ballsWithCoefficients);
ballCoefficientSum.put(ballNumber, coefficientSum);
log.debug("球号{}的ballNumbersWithCoefficients系数和{}", ballNumber, coefficientSum);
}
// 按系数和分组(从高到低排序)
Map<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
// 按系数和从高到低处理
for (Map.Entry<Double, List<Integer>> group : coefficientGroups.entrySet()) {
Double coefficientSum = group.getKey();
List<Integer> 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<BallWithCoefficient> 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<Integer> candidateBalls,
int selectCount, List<BallWithCoefficient> ballsWithCoefficients) {
log.debug("使用ballNumbersWithCoefficients系数和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount);
if (candidateBalls.size() <= selectCount) {
return new FilteringDetailResult(new ArrayList<>(candidateBalls), "");
}
// 计算每个球号的系数和
Map<Integer, Double> ballCoefficientSum = new HashMap<>();
for (Integer ballNumber : candidateBalls) {
double coefficientSum = calculateBallNumbersWithCoefficientsSum(ballNumber, ballsWithCoefficients);
ballCoefficientSum.put(ballNumber, coefficientSum);
log.debug("球号{}的ballNumbersWithCoefficients系数和{}", ballNumber, coefficientSum);
}
// 按系数和分组(从高到低排序)
Map<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
StringBuilder detailInfo = new StringBuilder();
// 先记录所有球号的系数和详细信息
for (Map.Entry<Double, List<Integer>> group : coefficientGroups.entrySet()) {
Double coefficientSum = group.getKey();
List<Integer> 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<Double, List<Integer>> group : coefficientGroups.entrySet()) {
Double coefficientSum = group.getKey();
List<Integer> 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<Integer> selectBallsByT3LineCoefficientSum(List<Integer> candidateBalls, int selectCount) {
log.debug("使用T3表线系数之和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount);
if (candidateBalls.size() <= selectCount) {
return new ArrayList<>(candidateBalls);
}
// 计算每个球号作为主球号时的线系数之和
Map<Integer, Double> ballCoefficientSum = new HashMap<>();
for (Integer ballNumber : candidateBalls) {
double coefficientSum = calculateT3LineCoefficientSum(ballNumber);
ballCoefficientSum.put(ballNumber, coefficientSum);
log.debug("球号{}的T3线系数之和{}", ballNumber, coefficientSum);
}
// 按线系数之和降序排序
List<Integer> sortedBalls = candidateBalls.stream()
.sorted((a, b) -> Double.compare(ballCoefficientSum.get(b), ballCoefficientSum.get(a)))
.collect(Collectors.toList());
log.debug("按T3线系数之和排序后的球号{}", sortedBalls);
// 按线系数之和分组
Map<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
// 按线系数之和从高到低处理
for (Map.Entry<Double, List<Integer>> group : coefficientGroups.entrySet()) {
Double coefficientSum = group.getKey();
List<Integer> 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<T3> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient");
List<T3> 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<Integer> fallowBallAnalysis(String level, List<Integer> firstThreeRedBalls,
List<Integer> 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<Integer> allNumbers = new ArrayList<>();
// 第一步处理第1个红球从T7表获取10个数字同时记录系数
log.info("第一步处理第1个红球{}从T7表获取10个数字使用{}级别算法", firstThreeRedBalls.get(0), level);
List<Integer> firstRedBallNumbers = getTop10FromT7(firstThreeRedBalls.get(0), level);
allNumbers.addAll(firstRedBallNumbers);
log.info("第1个红球{}获取到{}个数字:{}", firstThreeRedBalls.get(0), firstRedBallNumbers.size(), firstRedBallNumbers);
// 同步记录上述10个数字在T7表中的面系数记录格式与analyzeBalls一致
List<BallWithCoefficient> 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<Integer> ballNumbers = getTop26FromT3(redBall);
allNumbers.addAll(ballNumbers);
log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers);
}
// 第三步从history_top_100获取前3个球号
log.info("第三步从history_top_100获取前3个球号");
List<Integer> top3Numbers = getTop3FromHistoryTop100();
allNumbers.addAll(top3Numbers);
log.info("从history_top_100获取到{}个数字:{}", top3Numbers.size(), top3Numbers);
// 第四步取出前3个红球号码的后两个
log.info("第四步取出前3个红球号码的后两个");
List<Integer> lastTwoOfFirstThree = Arrays.asList(firstThreeRedBalls.get(1), firstThreeRedBalls.get(2));
allNumbers.addAll(lastTwoOfFirstThree);
log.info("前3个红球的后两个{}", lastTwoOfFirstThree);
// 第五步用上期蓝球从T4表获取26个蓝球号码
log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", blueBall);
List<Integer> blueNumbers = getTop26FromT4(blueBall);
allNumbers.addAll(blueNumbers);
log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers);
log.info("总共收集到{}个数字", allNumbers.size());
// 第六步统计频率并获取前10个若同频超过10个用第一步T7面系数作为二次筛选依据
List<Integer> 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<Integer> firstThreeRedBalls,
List<Integer> 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<Integer> allNumbers = new ArrayList<>();
// 第一步处理第1个红球从T7表获取10个数字同时记录系数
log.info("第一步处理第1个红球{}从T7表获取10个数字使用{}级别算法", firstThreeRedBalls.get(0), level);
List<Integer> firstRedBallNumbers = getTop10FromT7(firstThreeRedBalls.get(0), level);
allNumbers.addAll(firstRedBallNumbers);
log.info("第1个红球{}获取到{}个数字:{}", firstThreeRedBalls.get(0), firstRedBallNumbers.size(), firstRedBallNumbers);
// 同步记录上述10个数字在T7表中的面系数记录格式与analyzeBalls一致
List<BallWithCoefficient> 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<Integer> ballNumbers = getTop26FromT3(redBall);
allNumbers.addAll(ballNumbers);
log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers);
}
// 第三步从history_top_100获取前3个球号
log.info("第三步从history_top_100获取前3个球号");
List<Integer> top3Numbers = getTop3FromHistoryTop100();
allNumbers.addAll(top3Numbers);
log.info("从history_top_100获取到{}个数字:{}", top3Numbers.size(), top3Numbers);
// 第四步取出前3个红球号码的后两个
log.info("第四步取出前3个红球号码的后两个");
List<Integer> lastTwoOfFirstThree = Arrays.asList(firstThreeRedBalls.get(1), firstThreeRedBalls.get(2));
allNumbers.addAll(lastTwoOfFirstThree);
log.info("前3个红球的后两个{}", lastTwoOfFirstThree);
// 第五步用上期蓝球从T4表获取26个蓝球号码
log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", blueBall);
List<Integer> 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<Integer> firstThreeRedBalls, List<Integer> 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<Integer> firstThreeSet = new HashSet<>(firstThreeRedBalls);
if (firstThreeSet.size() != 3) {
throw new IllegalArgumentException("前3个红球号码不能重复");
}
// 验证后6个红球不重复
Set<Integer> 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<Integer> getTop10FromT7(Integer masterBallNumber, String level) {
log.debug("从T7表查询主球{}的面系数数据,级别:{}", masterBallNumber, level);
// 查询指定主球的所有数据,按面系数降序排列
QueryWrapper<T7> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("faceCoefficient")
.orderByAsc("slaveBallNumber"); // 面系数相同时按从球号升序
List<T7> t7List = t7Mapper.selectList(queryWrapper);
if (t7List.isEmpty()) {
log.warn("T7表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<Integer> 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<Integer> getHighLevelBallsFromT7(List<T7> t7List) {
if (t7List.size() < 10) {
log.warn("T7表数据不足10条实际{}条", t7List.size());
return t7List.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList());
}
// 获取前9个
List<Integer> result = new ArrayList<>();
for (int i = 0; i < 9; i++) {
result.add(t7List.get(i).getSlaveBallNumber());
}
// 处理第10个位置检查是否有相同的面系数
Double targetCoefficient = t7List.get(9).getFaceCoefficient();
List<T7> 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<Integer> getMiddleLevelBallsFromT7(List<T7> 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<Integer> candidates = new ArrayList<>();
for (int i = startIndex; i <= endIndex && candidates.size() < 10; i++) {
candidates.add(t7List.get(i).getSlaveBallNumber());
}
// 处理边界位置的相同面系数情况(包含边界本身参与比较,并带回退规则)
List<Integer> result = handleT7BoundaryConflicts(t7List, candidates, startIndex, endIndex);
log.debug("T7中位算法选择范围[{}, {}],共{}个球", startIndex, endIndex, result.size());
return result;
}
/**
* 低位算法从T7表取面系数最小值向上第3-13个球共10个球号
*/
private List<Integer> getLowLevelBallsFromT7(List<T7> 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<Integer> candidates = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
candidates.add(t7List.get(i).getSlaveBallNumber());
}
// 处理边界位置的相同面系数情况
List<Integer> result = handleT7BoundaryConflicts(t7List, candidates, startIndex, endIndex);
log.debug("T7低位算法选择范围[{}, {}],共{}个球", startIndex, endIndex, result.size());
return result;
}
/**
* 处理T7表边界位置的面系数冲突
*/
private List<Integer> handleT7BoundaryConflicts(List<T7> t7List, List<Integer> candidates,
int startIndex, int endIndex) {
List<Integer> result = new ArrayList<>(candidates);
if (result.size() >= 10) {
// 检查第1个位置最大面系数的冲突包含边界本身
Double firstCoefficient = t7List.get(startIndex).getFaceCoefficient();
List<T7> 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<T7> 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<Integer> getTop26FromT3(Integer masterBallNumber) {
log.debug("从T3表查询主球{}的线系数数据取前26个", masterBallNumber);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T3> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T3> 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<Integer> result = new ArrayList<>();
for (int i = 0; i < 25; i++) {
result.add(t3List.get(i).getSlaveBallNumber());
}
// 处理第26个位置检查是否有相同的线系数
Double targetCoefficient = t3List.get(25).getLineCoefficient();
List<T3> 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<Integer> getTop3FromHistoryTop100() {
log.debug("从history_top_100表获取前3个球号");
// 查询前3个点系数最高的球号
QueryWrapper<HistoryTop100> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber") // 点系数相同时按球号升序
.last("LIMIT 3"); // 限制只返回前3条记录
List<HistoryTop100> top100List = historyTop100Mapper.selectList(queryWrapper);
if (top100List.size() < 3) {
log.warn("history_top_100表数据不足3条实际{}条", top100List.size());
}
List<Integer> 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<Integer> getTop10FromT4(Integer masterBallNumber, String level) {
log.debug("从T4表查询主球{}的线系数数据,级别:{}", masterBallNumber, level);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T4> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T4> t4List = t4Mapper.selectList(queryWrapper);
if (t4List.isEmpty()) {
log.warn("T4表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<Integer> 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<Integer> getHighLevelBallsFromT4For10(List<T4> t4List) {
if (t4List.size() < 10) {
log.warn("T4表数据不足10条实际{}条", t4List.size());
return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList());
}
// 获取前9个
List<Integer> result = new ArrayList<>();
for (int i = 0; i < 9; i++) {
result.add(t4List.get(i).getSlaveBallNumber());
}
// 处理第10个位置检查是否有相同的线系数
Double targetCoefficient = t4List.get(9).getLineCoefficient();
List<T4> 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<Integer> getMiddleLevelBallsFromT4For10(List<T4> 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<Integer> candidates = new ArrayList<>();
for (int i = startIndex; i <= endIndex && candidates.size() < 10; i++) {
candidates.add(t4List.get(i).getSlaveBallNumber());
}
// 处理边界位置的相同线系数情况
List<Integer> result = handleT4BoundaryConflictsFor10(t4List, candidates, startIndex, endIndex);
log.debug("T4中位算法选择范围[{}, {}],共{}个球", startIndex, endIndex, result.size());
return result;
}
/**
* 低位算法从T4表取线系数最小值向上第4-14个球的10个球号
*/
private List<Integer> getLowLevelBallsFromT4For10(List<T4> 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<Integer> candidates = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
candidates.add(t4List.get(i).getSlaveBallNumber());
}
// 处理边界位置的相同线系数情况
List<Integer> result = handleT4BoundaryConflictsFor10(t4List, candidates, startIndex, endIndex);
log.debug("T4低位算法选择范围[{}, {}],共{}个球", startIndex, endIndex, result.size());
return result;
}
/**
* 统计数字出现频率返回频率最高的前10个数字
* 如果频次相同的球号超过10个使用T7表面系数进行二次筛选
*/
private List<Integer> getTop10ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> step1BallsWithCoefficients) {
log.debug("统计{}个数字的出现频率", allNumbers.size());
// 统计频率
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (Integer number : allNumbers) {
frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1);
}
log.debug("数字频率统计:{}", frequencyMap);
// 按频率分组
Map<Integer, List<Integer>> frequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
Integer frequency = entry.getValue();
Integer ballNumber = entry.getKey();
frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber);
}
log.debug("按频率分组:{}", frequencyGroups);
List<Integer> result = new ArrayList<>();
// 按频率从高到低处理
for (Map.Entry<Integer, List<Integer>> group : frequencyGroups.entrySet()) {
Integer frequency = group.getKey();
List<Integer> 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<Integer, Double> num2Coeff = new HashMap<>();
if (step1BallsWithCoefficients != null) {
for (BallWithCoefficient bwc : step1BallsWithCoefficients) {
num2Coeff.put(bwc.getBallNumber(), bwc.getCoefficient());
}
}
// 先按面系数降序,再按球号升序
List<Integer> 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<Integer> higherThanThreshold = new ArrayList<>();
List<Integer> 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<Integer> 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<Integer> allNumbers, List<BallWithCoefficient> step1BallsWithCoefficients) {
log.debug("统计{}个数字的出现频率", allNumbers.size());
// 统计频率
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (Integer number : allNumbers) {
frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1);
}
log.debug("数字频率统计:{}", frequencyMap);
// 按频率分组
Map<Integer, List<Integer>> frequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
Integer frequency = entry.getValue();
Integer ballNumber = entry.getKey();
frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber);
}
log.debug("按频率分组:{}", frequencyGroups);
List<Integer> result = new ArrayList<>();
List<Integer> directlySelectedBalls = new ArrayList<>();
List<Integer> multiLevelCandidates = new ArrayList<>();
List<Integer> secondaryFilteredBalls = new ArrayList<>();
boolean needMultiLevelFiltering = false;
StringBuilder processDescription = new StringBuilder();
String detailedStepsInfo = "";
// 按频率从高到低处理
for (Map.Entry<Integer, List<Integer>> group : frequencyGroups.entrySet()) {
Integer frequency = group.getKey();
List<Integer> 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<Integer> allParticipatedBalls = new ArrayList<>();
allParticipatedBalls.addAll(directlySelectedBalls);
allParticipatedBalls.addAll(multiLevelCandidates);
Map<Integer, List<Integer>> participatedFrequencyGroups = new LinkedHashMap<>();
for (Integer ball : allParticipatedBalls) {
Integer frequency = frequencyMap.get(ball);
participatedFrequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ball);
}
// 按频率从高到低排序
Map<Integer, List<Integer>> sortedParticipatedFrequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, List<Integer>> entry : participatedFrequencyGroups.entrySet()) {
Integer frequency = entry.getKey();
List<Integer> 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<Integer> selectBallsByT7FaceCoefficient(List<Integer> candidateBalls, int selectCount) {
log.info("开始根据T7表面系数总和选择球号候选{},需要选择:{}", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号");
return new ArrayList<>(candidateBalls);
}
// 第一级:计算每个球号作为主球时的面系数总和
Map<Integer, Double> faceCoefficientSumMap = new HashMap<>();
for (Integer ballNumber : candidateBalls) {
QueryWrapper<T7> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", ballNumber);
List<T7> t7List = t7Mapper.selectList(queryWrapper);
double sum = t7List.stream()
.mapToDouble(T7::getFaceCoefficient)
.sum();
faceCoefficientSumMap.put(ballNumber, sum);
log.debug("球号{}作为主球的面系数总和:{}", ballNumber, sum);
}
// 按面系数总和分组
Map<Double, List<Integer>> faceCoefficientGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Double> entry : faceCoefficientSumMap.entrySet()) {
Double sum = entry.getValue();
Integer ballNumber = entry.getKey();
faceCoefficientGroups.computeIfAbsent(sum, k -> new ArrayList<>()).add(ballNumber);
}
log.info("T7表面系数总和分组{}", faceCoefficientGroups);
List<Integer> result = new ArrayList<>();
// 按面系数总和从高到低处理
for (Map.Entry<Double, List<Integer>> group : faceCoefficientGroups.entrySet()) {
Double faceSum = group.getKey();
List<Integer> 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<Integer> 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<Integer> selectBallsByMultiLevelFilteringFor8(List<Integer> candidateBalls, int selectCount) {
log.info("开始多级筛选8个球版本候选{},需要选择:{}", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号");
return new ArrayList<>(candidateBalls);
}
// 第二级使用history_top_100表的排名进行筛选
List<Integer> 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<Integer> filteredByTop = selectBallsByHistoryTopPointCoefficient(filteredByTop100, selectCount);
if (filteredByTop.size() == selectCount) {
log.info("通过history_top表筛选成功结果{}", filteredByTop);
return filteredByTop;
}
// 第四级:随机选择
log.info("history_top表筛选后仍有{}个球号,进行随机选择", filteredByTop.size());
List<Integer> finalResult = selectBallsRandomly(filteredByTop, selectCount);
log.info("随机选择完成,最终结果:{}", finalResult);
return finalResult;
}
/**
* 根据history_top_100表的排名筛选球号
* @param candidateBalls 候选球号列表
* @param selectCount 需要选择的数量
* @return 筛选后的球号列表
*/
private List<Integer> selectBallsByHistoryTop100Ranking(List<Integer> candidateBalls, int selectCount) {
log.info("使用history_top_100表排名筛选球号候选{},需要选择:{}", candidateBalls, selectCount);
// 查询这些球号在history_top_100表中的点系数
QueryWrapper<HistoryTop100> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<HistoryTop100> 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<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
// 按点系数从高到低处理
for (Map.Entry<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Integer> 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<Integer> candidateBalls, int selectCount) {
log.info("使用history_top_100表排名筛选球号候选{},需要选择:{}", candidateBalls, selectCount);
// 查询这些球号在history_top_100表中的点系数
QueryWrapper<HistoryTop100> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<HistoryTop100> 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<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
StringBuilder detailInfo = new StringBuilder();
// 先记录所有球号的排位详细信息
for (Map.Entry<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Integer> notInTop100 = new ArrayList<>(candidateBalls);
List<Integer> 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<Integer> selectBallsByHistoryTopPointCoefficient(List<Integer> candidateBalls, int selectCount) {
log.info("使用history_top表点系数筛选球号候选{},需要选择:{}", candidateBalls, selectCount);
// 查询这些球号在history_top表中的点系数
QueryWrapper<HistoryTop> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<HistoryTop> 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<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
// 按点系数从高到低处理
for (Map.Entry<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Integer> 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<Integer> candidateBalls, int selectCount) {
log.info("使用history_top表点系数筛选球号候选{},需要选择:{}", candidateBalls, selectCount);
// 查询这些球号在history_top表中的点系数
QueryWrapper<HistoryTop> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<HistoryTop> 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<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
StringBuilder detailInfo = new StringBuilder();
// 先记录所有球号的点系数详细信息
for (Map.Entry<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Integer> notInHistoryTop = new ArrayList<>(candidateBalls);
List<Integer> 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<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Integer> selectBallsRandomly(List<Integer> candidateBalls, int selectCount) {
log.info("随机选择球号,候选:{},需要选择:{}", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
log.info("候选球号数量不超过需要选择的数量,直接返回所有候选球号");
return new ArrayList<>(candidateBalls);
}
// 创建候选球号的副本并打乱顺序
List<Integer> shuffledBalls = new ArrayList<>(candidateBalls);
Collections.shuffle(shuffledBalls, new Random(System.currentTimeMillis()));
// 选择前selectCount个
List<Integer> result = shuffledBalls.subList(0, selectCount);
// 按球号升序排序结果
Collections.sort(result);
log.info("随机选择结果:{}", result);
return result;
}
/**
* 仅在一组同T7系数的并列候选中按“先100期再历史最后随机”的规则补齐目标数量。
* 行为符合“100期先拿能拿的直接加入不足时再从剩余里用history_top补齐仍不足再随机补齐”。
*/
private List<Integer> selectByTop100ThenHistoryTop(List<Integer> candidateBalls, int needCount) {
if (candidateBalls == null || candidateBalls.isEmpty() || needCount <= 0) {
return new ArrayList<>();
}
// 在同T7系数的候选中先用100期逐组加入若某组加入会超出则该组整体作为未决集合交给下一层(history_top)
QueryWrapper<HistoryTop100> query = new QueryWrapper<>();
query.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<HistoryTop100> top100List = historyTop100Mapper.selectList(query);
List<Integer> selectedByTop100 = new ArrayList<>();
List<Integer> undecidedGroup = new ArrayList<>();
if (top100List != null && !top100List.isEmpty()) {
// 分组(保持顺序)
Map<Double, List<Integer>> groups = new LinkedHashMap<>();
for (HistoryTop100 item : top100List) {
groups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>())
.add(item.getBallNumber());
}
for (Map.Entry<Double, List<Integer>> entry : groups.entrySet()) {
List<Integer> 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<Integer> 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<Integer> byTop = forHistoryTop.isEmpty() ? new ArrayList<>()
: selectBallsByHistoryTopPointCoefficient(forHistoryTop, stillNeed);
List<Integer> 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<Integer> left = new ArrayList<>(candidateBalls);
left.removeAll(collected);
if (!left.isEmpty()) {
List<Integer> 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<Integer> blueBallAnalysis(String level, List<Integer> predictedRedBalls,
List<Integer> predictedBlueBalls, List<Integer> lastRedBalls,
Integer lastBlueBall) {
log.info("开始蓝球分析算法,级别:{},预测红球:{},预测蓝球:{},上期红球:{},上期蓝球:{}",
level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall);
// 验证输入参数
validateBlueBallAnalysisParams(level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall);
List<Integer> 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<Integer> 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<Integer> ballNumbers = getSimpleTop5FromT8ByLevel(redBall, level);
allNumbers.addAll(ballNumbers);
// 额外记录这5个蓝球的面系数与 fallowBallAnalysis 第一步风格一致)
List<BallWithCoefficient> 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<Integer> 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<Integer> blueNumbers = getTop12FromT5(lastBlueBall);
allNumbers.addAll(blueNumbers);
log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers);
log.info("总共收集到{}个蓝球号码", allNumbers.size());
// 汇总第二步收集的T8面系数供第六步同频二次筛选使用
List<BallWithCoefficient> step2BlueT8Coefficients = new ArrayList<>();
// 由于上面的循环内使用了临时变量 t8Coeffs这里重新按预测红球聚合一次确保完整
for (Integer redBall : predictedRedBalls) {
List<Integer> tmpBalls = getSimpleTop5FromT8ByLevel(redBall, level);
step2BlueT8Coefficients.addAll(getT8CoefficientsFor(redBall, tmpBalls));
}
// 第六步统计频率并获取前4个同频时使用第2步收集的T8系数求和作为第一级筛选
List<Integer> 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<Integer> predictedRedBalls,
List<Integer> predictedBlueBalls, List<Integer> lastRedBalls,
Integer lastBlueBall) {
log.info("开始蓝球分析算法(带过程说明),级别:{},预测红球:{},预测蓝球:{},上期红球:{},上期蓝球:{}",
level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall);
// 验证输入参数
validateBlueBallAnalysisParams(level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall);
List<Integer> 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<Integer> 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<Integer> ballNumbers = getSimpleTop5FromT8ByLevel(redBall, level);
allNumbers.addAll(ballNumbers);
// 额外记录这5个蓝球的面系数与 fallowBallAnalysis 第一步风格一致)
List<BallWithCoefficient> 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<Integer> 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<Integer> blueNumbers = getTop12FromT5(lastBlueBall);
allNumbers.addAll(blueNumbers);
log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers);
log.info("总共收集到{}个蓝球号码", allNumbers.size());
// 汇总第二步收集的T8面系数供第六步同频二次筛选使用
List<BallWithCoefficient> step2BlueT8Coefficients = new ArrayList<>();
// 由于上面的循环内使用了临时变量 t8Coeffs这里重新按预测红球聚合一次确保完整
for (Integer redBall : predictedRedBalls) {
List<Integer> 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<Integer> predictedRedBalls,
List<Integer> predictedBlueBalls, List<Integer> 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<Integer> getTop12FromT6(Integer masterBallNumber) {
log.debug("从T6表查询主球{}的线系数数据", masterBallNumber);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T6> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T6> 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<Integer> result = new ArrayList<>();
for (int i = 0; i < 11; i++) {
result.add(t6List.get(i).getSlaveBallNumber());
}
// 处理第12个位置检查是否有相同的线系数
Double targetCoefficient = t6List.get(11).getLineCoefficient();
List<T6> 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<Integer> 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<BlueHistoryTop100> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidates)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<BlueHistoryTop100> 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<Integer> selectBlueBallsWithFallback(List<Integer> 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<BlueHistoryTop100> top100Query = new QueryWrapper<>();
top100Query.in("ballNumber", candidates)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<BlueHistoryTop100> 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<Integer> selectedBy100 = top100List == null ? new ArrayList<>()
: top100List.stream().map(BlueHistoryTop100::getBallNumber).collect(Collectors.toList());
List<Integer> remaining = new ArrayList<>(candidates);
remaining.removeAll(selectedBy100);
int stillNeed = needCount - selectedBy100.size();
QueryWrapper<BlueHistoryTop> topQuery = new QueryWrapper<>();
topQuery.in("ballNumber", remaining)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<BlueHistoryTop> topList = blueHistoryTopMapper.selectList(topQuery);
List<Integer> selectedByTop = topList == null ? new ArrayList<>()
: topList.stream().limit(stillNeed).map(BlueHistoryTop::getBallNumber).collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
result.addAll(selectedBy100);
result.addAll(selectedByTop);
// 仍不足则随机补齐
int gap = needCount - result.size();
if (gap > 0) {
List<Integer> 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<Integer> getSimpleTop5FromT8ByLevel(Integer masterBallNumber, String level) {
log.debug("从T8表查询主球{}的面系数数据,级别:{}", masterBallNumber, level);
// 查询指定主球的所有数据,按面系数降序排列
QueryWrapper<T8> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("faceCoefficient")
.orderByAsc("slaveBallNumber"); // 面系数相同时按从球号升序
List<T8> t8List = t8Mapper.selectList(queryWrapper);
if (t8List.isEmpty()) {
log.warn("T8表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<Integer> 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<T8> 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<T8> 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<T8> 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<Integer> baseResult = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
baseResult.add(t8List.get(i).getSlaveBallNumber());
}
// 处理顶部位置(系数最大的)毛边:向上查找相同面系数
Double topCoeff = t8List.get(startIndex).getFaceCoefficient();
List<T8> 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<Integer> 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<T8> 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<Integer> 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<Integer> getTop2FromBlueHistoryTop100() {
log.debug("从blue_history_top_100表获取前2个蓝球号码");
// 查询所有数据,按点系数降序排列
QueryWrapper<BlueHistoryTop100> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<BlueHistoryTop100> top100List = blueHistoryTop100Mapper.selectList(queryWrapper);
if (top100List.isEmpty()) {
log.warn("blue_history_top_100表中没有数据");
return new ArrayList<>();
}
if (top100List.size() == 1) {
List<Integer> result = Arrays.asList(top100List.get(0).getBallNumber());
log.debug("blue_history_top_100表只有1条数据{}", result);
return result;
}
List<Integer> 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<BlueHistoryTop100> 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<Integer> getSimpleTop5FromT5ByLevel(Integer masterBallNumber, String level) {
log.debug("从T5表查询主球{}的线系数数据,级别:{}", masterBallNumber, level);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T5> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T5> t5List = t5Mapper.selectList(queryWrapper);
if (t5List.isEmpty()) {
log.warn("T5表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<Integer> 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<Integer> getSimpleTop4ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> step2BlueT8Coefficients) {
log.debug("统计{}个蓝球号码的出现频率", allNumbers.size());
// 统计频率
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (Integer number : allNumbers) {
frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1);
}
log.debug("蓝球号码频率统计:{}", frequencyMap);
// 按频率分组
Map<Integer, List<Integer>> frequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
Integer frequency = entry.getValue();
Integer ballNumber = entry.getKey();
frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber);
}
log.debug("按频率分组:{}", frequencyGroups);
List<Integer> result = new ArrayList<>();
// 按频率从高到低处理
for (Map.Entry<Integer, List<Integer>> group : frequencyGroups.entrySet()) {
Integer frequency = group.getKey();
List<Integer> 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<Integer, Double> 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<Integer> 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<Integer> allNumbers, List<BallWithCoefficient> step2BlueT8Coefficients) {
log.debug("统计{}个蓝球号码的出现频率", allNumbers.size());
// 统计频率
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (Integer number : allNumbers) {
frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1);
}
log.debug("蓝球号码频率统计:{}", frequencyMap);
// 按频率分组
Map<Integer, List<Integer>> frequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
Integer frequency = entry.getValue();
Integer ballNumber = entry.getKey();
frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber);
}
log.debug("按频率分组:{}", frequencyGroups);
List<Integer> result = new ArrayList<>();
List<Integer> directlySelectedBalls = new ArrayList<>();
List<Integer> multiLevelCandidates = new ArrayList<>();
List<Integer> secondaryFilteredBalls = new ArrayList<>();
boolean needMultiLevelFiltering = false;
StringBuilder processDescription = new StringBuilder();
String detailedStepsInfo = "";
// 构建参与筛选的球号频率分布描述
List<Integer> allParticipatedBalls = new ArrayList<>();
Map<Integer, List<Integer>> participatedFrequencyGroups = new LinkedHashMap<>();
// 按频率从高到低处理
for (Map.Entry<Integer, List<Integer>> group : frequencyGroups.entrySet()) {
Integer frequency = group.getKey();
List<Integer> 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<Integer, List<Integer>> sortedParticipatedFrequencyGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, List<Integer>> entry : participatedFrequencyGroups.entrySet()) {
Integer frequency = entry.getKey();
List<Integer> 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<Integer> 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<BlueHistoryTop> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidates)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<BlueHistoryTop> 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<Integer> selectBallsByMultiLevelFilteringForBlue4(List<Integer> candidateBalls, int selectCount) {
log.info("开始四级筛选蓝球号码,候选:{},需要选择:{}", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
log.info("候选蓝球号码数量不超过需要选择的数量,直接返回所有候选蓝球号码");
return new ArrayList<>(candidateBalls);
}
// 第一级使用T8表的面系数总和进行筛选
List<Integer> 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<Integer> 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<Integer> filteredByTop = selectBallsByBlueHistoryTopPointCoefficientForBlue4(filteredByTop100, selectCount);
if (filteredByTop.size() == selectCount) {
log.info("通过blue_history_top表筛选成功结果{}", filteredByTop);
return filteredByTop;
}
// 第四级:随机选择
log.info("blue_history_top表筛选后仍有{}个蓝球号码,进行随机选择", filteredByTop.size());
List<Integer> finalResult = selectBallsRandomlyForBlue4(filteredByTop, selectCount);
log.info("随机选择完成,最终结果:{}", finalResult);
return finalResult;
}
/**
* 根据T8表的面系数总和选择蓝球号码第一级筛选
* @param candidateBalls 候选蓝球号码列表
* @param selectCount 需要选择的数量
* @return 选择的蓝球号码列表
*/
private List<Integer> selectBallsByT8FaceCoefficientSumForBlue4(List<Integer> candidateBalls, int selectCount) {
log.info("开始根据T8表面系数总和选择蓝球号码候选{},需要选择:{}", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
log.info("候选蓝球号码数量不超过需要选择的数量,直接返回所有候选蓝球号码");
return new ArrayList<>(candidateBalls);
}
// 计算每个蓝球号码作为主球时的面系数总和
Map<Integer, Double> faceCoefficientSumMap = new HashMap<>();
for (Integer ballNumber : candidateBalls) {
QueryWrapper<T8> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", ballNumber);
List<T8> t8List = t8Mapper.selectList(queryWrapper);
double sum = t8List.stream()
.mapToDouble(T8::getFaceCoefficient)
.sum();
faceCoefficientSumMap.put(ballNumber, sum);
log.debug("蓝球号码{}作为主球的面系数总和:{}", ballNumber, sum);
}
// 按面系数总和分组
Map<Double, List<Integer>> faceCoefficientGroups = new TreeMap<>(Collections.reverseOrder());
for (Map.Entry<Integer, Double> entry : faceCoefficientSumMap.entrySet()) {
Double sum = entry.getValue();
Integer ballNumber = entry.getKey();
faceCoefficientGroups.computeIfAbsent(sum, k -> new ArrayList<>()).add(ballNumber);
}
log.info("T8表面系数总和分组{}", faceCoefficientGroups);
List<Integer> result = new ArrayList<>();
// 按面系数总和从高到低处理
for (Map.Entry<Double, List<Integer>> group : faceCoefficientGroups.entrySet()) {
Double faceSum = group.getKey();
List<Integer> 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<Integer> selectBallsByBlueHistoryTop100RankingForBlue4(List<Integer> candidateBalls, int selectCount) {
log.info("使用blue_history_top_100表点系数筛选蓝球号码候选{},需要选择:{}", candidateBalls, selectCount);
// 查询这些球号在blue_history_top_100表中的点系数
QueryWrapper<BlueHistoryTop100> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<BlueHistoryTop100> 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<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
// 按点系数从高到低处理
for (Map.Entry<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Integer> 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<Integer> selectBallsByBlueHistoryTopPointCoefficientForBlue4(List<Integer> candidateBalls, int selectCount) {
log.info("使用blue_history_top表点系数筛选蓝球号码候选{},需要选择:{}", candidateBalls, selectCount);
// 查询这些球号在blue_history_top表中的点系数
QueryWrapper<BlueHistoryTop> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ballNumber", candidateBalls)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber"); // 点系数相同时按球号升序
List<BlueHistoryTop> 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<Double, List<Integer>> 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<Integer> result = new ArrayList<>();
// 按点系数从高到低处理
for (Map.Entry<Double, List<Integer>> group : pointCoefficientGroups.entrySet()) {
Double pointCoefficient = group.getKey();
List<Integer> 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<Integer> 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<Integer> selectBallsRandomlyForBlue4(List<Integer> candidateBalls, int selectCount) {
log.info("随机选择蓝球号码,候选:{},需要选择:{}", candidateBalls, selectCount);
if (candidateBalls.size() <= selectCount) {
log.info("候选蓝球号码数量不超过需要选择的数量,直接返回所有候选蓝球号码");
return new ArrayList<>(candidateBalls);
}
// 创建候选球号的副本并打乱顺序
List<Integer> shuffledBalls = new ArrayList<>(candidateBalls);
Collections.shuffle(shuffledBalls, new Random(System.currentTimeMillis()));
// 选择前selectCount个
List<Integer> 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<PredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId)
.ne("predictStatus", "待开奖");
List<PredictRecord> 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<Integer> 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<PredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId)
.ne("predictStatus", "待开奖");
List<PredictRecord> 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<PredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId);
// 排除待开奖状态
queryWrapper.ne("predictStatus", "待开奖");
// 如果指定了预测记录ID则只查询该记录
if (predictId != null) {
queryWrapper.eq("id", predictId);
}
// 查询用户的预测记录
List<PredictRecord> records = predictRecordMapper.selectList(queryWrapper);
// 按中奖等级分组统计
Map<String, Integer> countByPrizeLevel = new HashMap<>();
Map<String, BigDecimal> 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<PrizeEstimateVO.PrizeDetailItem> 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<PredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId)
.ne("predictStatus", "待开奖");
List<PredictRecord> 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<Integer> predictedRedBalls = Arrays.asList(
record.getRedBall1(),
record.getRedBall2(),
record.getRedBall3(),
record.getRedBall4(),
record.getRedBall5(),
record.getRedBall6()
);
List<Integer> 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<Integer> getTop26FromT4(Integer masterBallNumber) {
log.debug("从T4表查询主球{}的线系数数据取前26个", masterBallNumber);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T4> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T4> 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<Integer> result = new ArrayList<>();
for (int i = 0; i < 25; i++) {
result.add(t4List.get(i).getSlaveBallNumber());
}
// 处理第26个位置检查是否有相同的线系数
Double targetCoefficient = t4List.get(25).getLineCoefficient();
List<T4> 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<Integer> getTop12FromT5(Integer masterBallNumber) {
log.debug("从T5表查询主球{}的线系数数据取前12个含毛边处理", masterBallNumber);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T5> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T5> 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<Integer> 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<T5> 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<Integer> 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<BlueHistoryTop100> qw = new QueryWrapper<>();
qw.in("ballNumber", eqNums)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<BlueHistoryTop100> top100 = blueHistoryTop100Mapper.selectList(qw);
List<Integer> 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<Double, List<Integer>> pcGroups = new TreeMap<>(Collections.reverseOrder());
Set<Integer> 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<Double, List<Integer>> e : pcGroups.entrySet()) {
if (picked.size() >= needFromEquals) break;
List<Integer> 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<BlueHistoryTop> qwTop = new QueryWrapper<>();
qwTop.in("ballNumber", group)
.orderByDesc("pointCoefficient")
.orderByAsc("ballNumber");
List<BlueHistoryTop> topRows = blueHistoryTopMapper.selectList(qwTop);
if (!topRows.isEmpty()) {
List<Integer> ordered = topRows.stream()
.map(BlueHistoryTop::getBallNumber)
.collect(Collectors.toList());
picked.addAll(ordered.subList(0, Math.min(remaining, ordered.size())));
} else {
// 若blue_history_top也没有按球号升序取
List<Integer> 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<Integer> 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<Integer> getTop17FromT4(Integer masterBallNumber) {
log.debug("从T4表查询主球{}的线系数数据取前17个", masterBallNumber);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T4> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient")
.orderByAsc("slaveBallNumber"); // 线系数相同时按从球号升序
List<T4> 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<Integer> result = new ArrayList<>();
for (int i = 0; i < 16; i++) {
result.add(t4List.get(i).getSlaveBallNumber());
}
// 处理第17个位置检查是否有相同的线系数
Double targetCoefficient = t4List.get(16).getLineCoefficient();
List<T4> 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;
}
}