算法修改

This commit is contained in:
lihanqi
2025-08-08 19:18:50 +08:00
parent c6bb187ae8
commit 1235a8eaf0
2 changed files with 596 additions and 133 deletions

View File

@@ -84,6 +84,53 @@ public class BallAnalysisService {
@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)
@@ -341,44 +388,50 @@ public class BallAnalysisService {
.collect(Collectors.toList());
}
// 获取前16
// 获取前17
List<BallWithCoefficient> result = new ArrayList<>();
for (int i = 0; i < 16; i++) {
for (int i = 0; i < 17 && i < t3List.size(); i++) {
T3 t3 = t3List.get(i);
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
// 处理第17个位置:检查是否有相同的线系数
Double targetCoefficient = t3List.get(16).getLineCoefficient();
// 处理第17个位置(系数最小的球号)的毛边情况
if (result.size() == 17) {
int lastIndex = 16; // 第17个位置的索引
Double lastCoefficient = result.get(lastIndex).getCoefficient();
// 查找第17个位置向下相同系数值的球号
List<T3> candidatesFor17th = new ArrayList<>();
// 找出所有线系数等于第17个位置线系数的记录
for (int i = 16; i < t3List.size(); i++) {
if (t3List.get(i).getLineCoefficient().equals(targetCoefficient)) {
// 添加第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; // 线系数不同停止查找
break; // 系数不同停止查找
}
}
if (candidatesFor17th.size() == 1) {
// 只有一个候选,直接添加
T3 t3 = candidatesFor17th.get(0);
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
} else {
// 有多个候选通过history_top_100表的点系数排序
log.debug("第17位有{}个相同线系数的候选:{}", candidatesFor17th.size(),
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对象并添加
// 找到对应的T3对象并替换第17个位置
T3 selectedT3 = candidatesFor17th.stream()
.filter(t3 -> t3.getSlaveBallNumber().equals(bestBall))
.findFirst()
.orElse(candidatesFor17th.get(0));
result.add(new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber));
result.set(lastIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber));
log.debug("第17个位置毛边处理完成最终选择{}", bestBall);
}
}
return result;
@@ -556,6 +609,89 @@ public class BallAnalysisService {
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
// 处理边界毛边情况
if (result.size() >= 8) {
// 处理向上8个球号的第8个位置索引为7的毛边情况
int upBoundaryIndex = 7; // 向上第8个球号的索引
if (upBoundaryIndex < result.size()) {
Double upBoundaryCoefficient = result.get(upBoundaryIndex).getCoefficient();
// 查找向上还有没有相同系数值的球号
List<T3> upCandidates = new ArrayList<>();
int actualUpIndex = startIndex + upBoundaryIndex;
// 添加边界球号本身
upCandidates.add(t3List.get(actualUpIndex));
// 向上查找相同系数值的球号
for (int i = actualUpIndex - 1; i >= 0; i--) {
if (t3List.get(i).getLineCoefficient().equals(upBoundaryCoefficient)) {
upCandidates.add(t3List.get(i));
} else {
break; // 系数不同就停止查找
}
}
if (upCandidates.size() > 1) {
log.debug("向上第8个球号及其上面有{}个相同线系数的候选:{}", upCandidates.size(),
upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100(
upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
);
// 找到对应的T3对象并替换第8个位置
T3 selectedT3 = upCandidates.stream()
.filter(t3 -> t3.getSlaveBallNumber().equals(bestBall))
.findFirst()
.orElse(upCandidates.get(0));
result.set(upBoundaryIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber));
log.debug("向上第8个球号毛边处理完成最终选择{}", bestBall);
}
}
// 处理向下8个球号的第8个位置的毛边情况
int downBoundaryIndex = Math.min(16, result.size() - 1); // 向下第8个球号的索引从中心点开始算
if (downBoundaryIndex >= 8 && downBoundaryIndex < result.size()) {
Double downBoundaryCoefficient = result.get(downBoundaryIndex).getCoefficient();
// 查找向下还有没有相同系数值的球号
List<T3> downCandidates = new ArrayList<>();
int actualDownIndex = startIndex + downBoundaryIndex;
// 添加边界球号本身
downCandidates.add(t3List.get(actualDownIndex));
// 向下查找相同系数值的球号
for (int i = actualDownIndex + 1; i < t3List.size(); i++) {
if (t3List.get(i).getLineCoefficient().equals(downBoundaryCoefficient)) {
downCandidates.add(t3List.get(i));
} else {
break; // 系数不同就停止查找
}
}
if (downCandidates.size() > 1) {
log.debug("向下第8个球号及其下面有{}个相同线系数的候选:{}", downCandidates.size(),
downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100(
downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
);
// 找到对应的T3对象并替换相应位置
T3 selectedT3 = downCandidates.stream()
.filter(t3 -> t3.getSlaveBallNumber().equals(bestBall))
.findFirst()
.orElse(downCandidates.get(0));
result.set(downBoundaryIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber));
log.debug("向下第8个球号毛边处理完成最终选择{}", bestBall);
}
}
}
log.debug("中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size());
return result;
}
@@ -627,33 +763,43 @@ public class BallAnalysisService {
candidates.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
// 处理第个位置(最大线系数)的相同值情况
if (candidates.size() >= 17) {
Double firstCoefficient = t3List.get(startIndex).getLineCoefficient();
List<T3> candidatesForFirst = new ArrayList<>();
// 处理第17个位置(系数最大的球号)的毛边情况
if (candidates.size() == 17) {
int lastIndex = 16; // 第17个位置的索引
Double lastCoefficient = candidates.get(lastIndex).getCoefficient();
int actualLastIndex = startIndex + lastIndex;
// 找出所有线系数等于第一个位置线系数的记录
for (int i = 0; i < t3List.size(); i++) {
if (t3List.get(i).getLineCoefficient().equals(firstCoefficient)) {
candidatesForFirst.add(t3List.get(i));
// 查找第17个位置向上相同系数值的球号
List<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 (candidatesForFirst.size() > 1) {
// 有多个候选通过history_top_100表的点系数排序
log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(),
candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
if (candidatesFor17th.size() > 1) {
log.debug("第17个位置及其向上有{}个相同线系数的候选:{}", candidatesFor17th.size(),
candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100(
candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
);
// 找到对应的T3对象并替换第个位置
T3 selectedT3 = candidatesForFirst.stream()
// 找到对应的T3对象并替换第17个位置
T3 selectedT3 = candidatesFor17th.stream()
.filter(t3 -> t3.getSlaveBallNumber().equals(bestBall))
.findFirst()
.orElse(candidatesForFirst.get(0));
candidates.set(0, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber));
.orElse(candidatesFor17th.get(0));
candidates.set(lastIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber));
log.debug("第17个位置毛边处理完成最终选择{}", bestBall);
}
}
@@ -833,28 +979,60 @@ public class BallAnalysisService {
}
/**
* 低位算法从T4表取线系数最小值向上第4-14个球的10个球号
* 低位算法从T4表取最小值向上17个球含最小值共17个球号
*/
private List<Integer> getLowLevelBallsFromT4(List<T4> t4List) {
if (t4List.size() < 14) {
log.warn("T4表数据不足14条,实际{}条", t4List.size());
if (t4List.size() < 17) {
log.warn("T4表数据不足17条,实际{}条", t4List.size());
return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList());
}
// 从最后14个开始取第4-14个即倒数第11到倒数第1个
int startIndex = Math.max(0, t4List.size() - 11);
int endIndex = t4List.size() - 1;
// 从最后17个开始(最小的线系数
int startIndex = Math.max(0, t4List.size() - 17);
List<Integer> candidates = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
for (int i = startIndex; i < t4List.size(); i++) {
candidates.add(t4List.get(i).getSlaveBallNumber());
}
// 处理边界位置的相同线系数情况
List<Integer> result = handleT4BoundaryConflictsFor10(t4List, candidates, startIndex, endIndex);
// 处理第17个位置系数最大的球号的毛边情况
if (candidates.size() == 17) {
int lastIndex = 16; // 第17个位置的索引
Double lastCoefficient = t4List.get(lastIndex).getLineCoefficient();
int actualLastIndex = lastIndex;
log.debug("T4低位算法选择范围[{}, {}],共{}个球", startIndex, endIndex, result.size());
return result;
// 查找第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 = selectBestBallFromHistoryTop100(
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()));
}
/**
@@ -1350,12 +1528,21 @@ public class BallAnalysisService {
List<Integer> allNumbers = new ArrayList<>();
// 第一步处理第1个红球从T7表获取10个数字
// 第一步处理第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++) {
@@ -1388,8 +1575,8 @@ public class BallAnalysisService {
log.info("总共收集到{}个数字", allNumbers.size());
// 第六步统计频率并获取前8个
List<Integer> result = getTop8ByFrequency(allNumbers);
// 第六步统计频率并获取前8个若同频超过8个用第一步T7面系数作为二次筛选依据
List<Integer> result = getTop8ByFrequency(allNumbers, step1BallsWithCoefficients);
log.info("跟随球号分析算法完成,结果:{}", result);
return result;
@@ -1936,7 +2123,7 @@ public class BallAnalysisService {
* 统计数字出现频率返回频率最高的前8个数字
* 如果频次相同的球号超过8个使用T7表面系数进行二次筛选
*/
private List<Integer> getTop8ByFrequency(List<Integer> allNumbers) {
private List<Integer> getTop8ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> step1BallsWithCoefficients) {
log.debug("统计{}个数字的出现频率", allNumbers.size());
// 统计频率
@@ -1976,14 +2163,32 @@ public class BallAnalysisService {
result.addAll(balls);
log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size());
} else {
// 会超过8个需要用T7表的面系数进行二次筛选
// 会超过8个需要用第一步记录的T7面系数进行二次筛选
int remainingSlots = 8 - result.size();
log.info("需要从{}个频率相同的球号中选择{}个使用T7面系数进行筛选", balls.size(), remainingSlots);
log.info("需要从{}个频率相同的球号中选择{}个,使用第一步T7面系数进行筛选找不到则按0处理", balls.size(), remainingSlots);
List<Integer> selectedBalls = selectBallsByT7FaceCoefficient(balls, remainingSlots);
// 构建 step1 球号->面系数 映射找不到则为0
Map<Integer, Double> num2Coeff = new HashMap<>();
if (step1BallsWithCoefficients != null) {
for (BallWithCoefficient bwc : step1BallsWithCoefficients) {
num2Coeff.put(bwc.getBallNumber(), bwc.getCoefficient());
}
}
// 先按面系数降序,再按球号升序
balls.sort((a, b) -> {
double ca = num2Coeff.getOrDefault(a, 0.0);
double cb = num2Coeff.getOrDefault(b, 0.0);
if (Double.compare(cb, ca) != 0) {
return Double.compare(cb, ca);
}
return Integer.compare(a, b);
});
List<Integer> selectedBalls = balls.subList(0, Math.min(remainingSlots, balls.size()));
result.addAll(selectedBalls);
log.info("通过T7面系数筛选完成,最终选择:{}", selectedBalls);
log.info("通过第一步T7面系数筛选完成最终选择{}", selectedBalls);
break; // 已经达到8个结束
}
@@ -2349,6 +2554,14 @@ public class BallAnalysisService {
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);
}
@@ -2362,16 +2575,24 @@ public class BallAnalysisService {
log.info("第四步添加预测的2个蓝球号码{}", predictedBlueBalls);
allNumbers.addAll(predictedBlueBalls);
// 第五步用上期蓝球从T4表获取26个蓝球号码
log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", lastBlueBall);
List<Integer> blueNumbers = getTop26FromT4(lastBlueBall);
// 第五步用上期蓝球从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());
// 第六步统计频率并获取前4个
List<Integer> result = getSimpleTop4ByFrequency(allNumbers);
// 汇总第二步收集的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;
@@ -2560,27 +2781,166 @@ public class BallAnalysisService {
int size = t8List.size();
switch (level) {
case "H":
// 高位:取面系数最大的5个
for (int i = 0; i < Math.min(5, size); i++) {
case "H": {
// 高位:系数最大值向下5个第5个位置毛边向下查找相同面系数使用blue_history_top_100比较
if (size < 5) {
log.warn("T8表数据不足5条实际{}条", size);
return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList());
}
// 前4个直接取
for (int i = 0; i < 4; i++) {
result.add(t8List.get(i).getSlaveBallNumber());
}
// 处理第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;
case "M":
// 中位取中间的5个
int start = Math.max(0, (size - 5) / 2);
for (int i = start; i < Math.min(start + 5, size); i++) {
result.add(t8List.get(i).getSlaveBallNumber());
}
}
if (candidatesFor5th.size() == 1) {
result.add(candidatesFor5th.get(0).getSlaveBallNumber());
} else {
log.debug("高位第5位有{}个相同面系数的候选:{}", candidatesFor5th.size(),
candidatesFor5th.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromBlueHistoryTop100(
candidatesFor5th.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())
);
result.add(bestBall);
}
break;
case "L":
// 低位取最后5个面系数最小的
int startLow = Math.max(0, size - 5);
for (int i = startLow; i < size; i++) {
}
case "M": {
// 中位取比平均值大的、且最近的一个为中心向上2个、向下2个共5个
// 上面两个的第一个(中心上方最近的)毛边向上查找;下面两个的第二个(更小的)毛边向下查找均用blue_history_top_100比较
if (size < 5) {
log.warn("T8表数据不足5条实际{}条", size);
return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList());
}
double avg = t8List.stream().mapToDouble(T8::getFaceCoefficient).average().orElse(0.0);
int avgIndex = -1;
double minDiff = Double.MAX_VALUE;
for (int i = 0; i < size; i++) {
double c = t8List.get(i).getFaceCoefficient();
if (c > avg) {
double diff = c - avg;
if (diff <= minDiff) { // 取最后一个最接近的
minDiff = diff;
avgIndex = i;
}
}
}
if (avgIndex == -1) {
avgIndex = 0;
}
int startIndex = Math.max(0, avgIndex - 2);
int endIndex = Math.min(size - 1, avgIndex + 2);
while (endIndex - startIndex + 1 < 5 && (startIndex > 0 || endIndex < size - 1)) {
if (startIndex > 0) startIndex--;
if (endIndex < size - 1 && endIndex - startIndex + 1 < 5) endIndex++;
}
for (int i = startIndex; i <= endIndex; i++) {
result.add(t8List.get(i).getSlaveBallNumber());
}
// 上面两个中的第一个(系数更大的那个):索引 startIndex即 avgIndex-2
int upFirstIndex = startIndex;
if (upFirstIndex >= 0) {
Double coeff = t8List.get(upFirstIndex).getFaceCoefficient();
List<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比较
if (size < 5) {
log.warn("T8表数据不足5条实际{}条", size);
return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList());
}
int endIndex = size - 2; // 倒数第二
int startIndex = Math.max(0, endIndex - 4);
for (int i = startIndex; i <= endIndex; i++) {
result.add(t8List.get(i).getSlaveBallNumber());
}
// 顶部位置(系数最大的)毛边处理:向上查找
Double topCoeff = t8List.get(startIndex).getFaceCoefficient();
List<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()));
Integer best = selectBestBallFromBlueHistoryTop100(
candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())
);
result.set(0, best);
}
break;
}
}
log.debug("T8表主球{}{}级别最终选择的{}个从球:{}", masterBallNumber, level, result.size(), result);
return result;
@@ -2707,7 +3067,7 @@ public class BallAnalysisService {
* 统计数字出现频率返回频率最高的前4个蓝球号码
* 如果频次相同的球号超过4个使用多级筛选机制
*/
private List<Integer> getSimpleTop4ByFrequency(List<Integer> allNumbers) {
private List<Integer> getSimpleTop4ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> step2BlueT8Coefficients) {
log.debug("统计{}个蓝球号码的出现频率", allNumbers.size());
// 统计频率
@@ -2747,14 +3107,35 @@ public class BallAnalysisService {
result.addAll(balls);
log.info("直接添加{}个蓝球号码,当前总数:{}", balls.size(), result.size());
} else {
// 会超过4个,需要用多级筛选机制
// 会超过4个第一级直接用“第二步收集的T8面系数求和”进行筛选
int remainingSlots = 4 - result.size();
log.info("需要从{}个频率相同的蓝球号码中选择{}个,使用多级筛选机制", balls.size(), remainingSlots);
log.info("需要从{}个频率相同的蓝球号码中选择{}个,使用第二步T8面系数求和进行筛选", balls.size(), remainingSlots);
List<Integer> selectedBalls = selectBallsByMultiLevelFilteringForBlue4(balls, 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("通过多级筛选完成,最终选择:{}", selectedBalls);
log.info("通过第二步T8面系数和筛选完成,最终选择:{}", selectedBalls);
break; // 已经达到4个结束
}
@@ -3445,7 +3826,7 @@ public class BallAnalysisService {
* @param masterBallNumber 主球号
*/
private List<Integer> getTop12FromT5(Integer masterBallNumber) {
log.debug("从T5表查询主球{}的线系数数据取前12个", masterBallNumber);
log.debug("从T5表查询主球{}的线系数数据取前12个(含毛边处理)", masterBallNumber);
// 查询指定主球的所有数据,按线系数降序排列
QueryWrapper<T5> queryWrapper = new QueryWrapper<>();
@@ -3465,37 +3846,119 @@ public class BallAnalysisService {
return t5List.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList());
}
// 获取前11个
// 完全复刻补齐策略:
// 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 < 11; i++) {
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;
}
// 处理第12个位置检查是否有相同的线系数
Double targetCoefficient = t5List.get(11).getLineCoefficient();
List<T5> candidatesFor12th = new ArrayList<>();
// 收集等于阈值的连续区间(紧随其后的相等区)
List<T5> equalsGroup = new ArrayList<>();
int idx = strictlyGreaterCount;
while (idx < t5List.size() && t5List.get(idx).getLineCoefficient().equals(targetCoefficient)) {
equalsGroup.add(t5List.get(idx));
idx++;
}
// 找出所有线系数等于第12个位置线系数的记录
for (int i = 11; i < t5List.size(); i++) {
if (t5List.get(i).getLineCoefficient().equals(targetCoefficient)) {
candidatesFor12th.add(t5List.get(i));
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 {
break; // 线系数不同,停止查找
// 在等于组中先用 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;
}
}
if (candidatesFor12th.size() == 1) {
// 只有一个候选,直接添加
result.add(candidatesFor12th.get(0).getSlaveBallNumber());
} else {
// 有多个候选通过blue_history_top_100表的点系数排序
log.debug("第12位有{}个相同线系数的候选:{}", candidatesFor12th.size(),
candidatesFor12th.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList()));
// 若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);
}
}
Integer bestBall = selectBestBallFromBlueHistoryTop100(
candidatesFor12th.stream().map(T5::getSlaveBallNumber).collect(Collectors.toList())
);
result.add(bestBall);
// 若仍不足12从更小系数的条目继续顺序补足
idx = strictlyGreaterCount + equalsGroup.size();
while (result.size() < 12 && idx < t5List.size()) {
result.add(t5List.get(idx).getSlaveBallNumber());
idx++;
}
log.debug("T5表主球{}最终选择的{}个从球:{}", masterBallNumber, result.size(), result);

View File

@@ -4,8 +4,8 @@ spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cpzs
username: root
password: root
username: cpzs_root
password: cpzs_123456
# datasource:
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://47.117.22.239:3306/cpzs?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&connectTimeout=10000&socketTimeout=30000
@@ -19,17 +19,17 @@ spring:
# max-lifetime: 1800000 # 连接的最大生命周期(毫秒)
# auto-commit: true # 自动提交
# connection-test-query: SELECT 1 # 测试连接是否可用的查询语句
data:
redis:
host: localhost
port: 6379
database: 0
# data:
# redis:
# host: 47.117.22.239
# host: localhost
# port: 6379
# database: 0
# password: cpzs_123456
data:
redis:
host: 47.117.22.239
port: 6379
database: 0
password: cpzs_123456
server:
port: 8123
servlet: