彩票助手1.3

This commit is contained in:
lihanqi
2025-08-12 17:19:51 +08:00
parent 1235a8eaf0
commit 7c2b23f1a2
2 changed files with 315 additions and 63 deletions

View File

@@ -419,7 +419,7 @@ public class BallAnalysisService {
log.debug("第17个位置及其向下有{}个相同线系数的候选:{}", candidatesFor17th.size(), log.debug("第17个位置及其向下有{}个相同线系数的候选:{}", candidatesFor17th.size(),
candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
); );
@@ -521,7 +521,7 @@ public class BallAnalysisService {
if (candidatesForFirst.size() > 1) { if (candidatesForFirst.size() > 1) {
log.debug("第一位及其上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), log.debug("第一位及其上面有{}个相同线系数的候选:{}", candidatesForFirst.size(),
candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
); );
result.set(0, bestBall); result.set(0, bestBall);
@@ -538,7 +538,7 @@ public class BallAnalysisService {
if (candidatesForLast.size() > 1) { if (candidatesForLast.size() > 1) {
log.debug("最后一位及其下面有{}个相同线系数的候选:{}", candidatesForLast.size(), log.debug("最后一位及其下面有{}个相同线系数的候选:{}", candidatesForLast.size(),
candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) candidatesForLast.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
); );
result.set(result.size() - 1, bestBall); result.set(result.size() - 1, bestBall);
@@ -611,19 +611,19 @@ public class BallAnalysisService {
// 处理边界毛边情况 // 处理边界毛边情况
if (result.size() >= 8) { if (result.size() >= 8) {
// 处理向上8个球号的第8个位置索引为7)的毛边情况 // 处理向上8个球号的第8个位置对应区间起点 startIndex即 result[0])的毛边情况
int upBoundaryIndex = 7; // 向上第8个球号的索引 int upBoundaryIndex = 0; // 向上第8个球号在 result 中的索引
if (upBoundaryIndex < result.size()) { if (upBoundaryIndex < result.size()) {
Double upBoundaryCoefficient = result.get(upBoundaryIndex).getCoefficient(); Double upBoundaryCoefficient = result.get(upBoundaryIndex).getCoefficient();
// 查找向上还有没有相同系数值的球号 // 查找向上还有没有相同系数值的球号
List<T3> upCandidates = new ArrayList<>(); List<T3> upCandidates = new ArrayList<>();
int actualUpIndex = startIndex + upBoundaryIndex; int actualUpIndex = startIndex + upBoundaryIndex; // 即 startIndex
// 添加边界球号本身 // 添加边界球号本身
upCandidates.add(t3List.get(actualUpIndex)); upCandidates.add(t3List.get(actualUpIndex));
// 向上查找相同系数值的球号 // 向上查找相同系数值的球号(在 startIndex 之上)
for (int i = actualUpIndex - 1; i >= 0; i--) { for (int i = actualUpIndex - 1; i >= 0; i--) {
if (t3List.get(i).getLineCoefficient().equals(upBoundaryCoefficient)) { if (t3List.get(i).getLineCoefficient().equals(upBoundaryCoefficient)) {
upCandidates.add(t3List.get(i)); upCandidates.add(t3List.get(i));
@@ -633,14 +633,14 @@ public class BallAnalysisService {
} }
if (upCandidates.size() > 1) { if (upCandidates.size() > 1) {
log.debug("向上第8个球号及其上面有{}个相同线系数的候选:{}", upCandidates.size(), log.debug("向上第8个球号startIndex及其上面有{}个相同线系数的候选:{}", upCandidates.size(),
upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
); );
// 找到对应的T3对象并替换第8个位置 // 找到对应的T3对象并替换 result[0]
T3 selectedT3 = upCandidates.stream() T3 selectedT3 = upCandidates.stream()
.filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall))
.findFirst() .findFirst()
@@ -651,19 +651,19 @@ public class BallAnalysisService {
} }
} }
// 处理向下8个球号的第8个位置的毛边情况 // 处理向下8个球号的第8个位置(对应区间终点 endIndex即 result[last]的毛边情况
int downBoundaryIndex = Math.min(16, result.size() - 1); // 向下第8个球号的索引(从中心点开始算) int downBoundaryIndex = Math.min(16, result.size() - 1); // 向下第8个球号在 result 中的索引
if (downBoundaryIndex >= 8 && downBoundaryIndex < result.size()) { if (downBoundaryIndex >= 8 && downBoundaryIndex < result.size()) {
Double downBoundaryCoefficient = result.get(downBoundaryIndex).getCoefficient(); Double downBoundaryCoefficient = result.get(downBoundaryIndex).getCoefficient();
// 查找向下还有没有相同系数值的球号 // 查找向下还有没有相同系数值的球号
List<T3> downCandidates = new ArrayList<>(); List<T3> downCandidates = new ArrayList<>();
int actualDownIndex = startIndex + downBoundaryIndex; int actualDownIndex = startIndex + downBoundaryIndex; // 即 endIndex
// 添加边界球号本身 // 添加边界球号本身
downCandidates.add(t3List.get(actualDownIndex)); downCandidates.add(t3List.get(actualDownIndex));
// 向下查找相同系数值的球号 // 向下查找相同系数值的球号(在 endIndex 之下)
for (int i = actualDownIndex + 1; i < t3List.size(); i++) { for (int i = actualDownIndex + 1; i < t3List.size(); i++) {
if (t3List.get(i).getLineCoefficient().equals(downBoundaryCoefficient)) { if (t3List.get(i).getLineCoefficient().equals(downBoundaryCoefficient)) {
downCandidates.add(t3List.get(i)); downCandidates.add(t3List.get(i));
@@ -673,14 +673,14 @@ public class BallAnalysisService {
} }
if (downCandidates.size() > 1) { if (downCandidates.size() > 1) {
log.debug("向下第8个球号及其下面有{}个相同线系数的候选:{}", downCandidates.size(), log.debug("向下第8个球号endIndex及其下面有{}个相同线系数的候选:{}", downCandidates.size(),
downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
); );
// 找到对应的T3对象并替换相应位置 // 找到对应的T3对象并替换 result[last]
T3 selectedT3 = downCandidates.stream() T3 selectedT3 = downCandidates.stream()
.filter(t3 -> t3.getSlaveBallNumber().equals(bestBall)) .filter(t3 -> t3.getSlaveBallNumber().equals(bestBall))
.findFirst() .findFirst()
@@ -726,11 +726,11 @@ public class BallAnalysisService {
} }
if (candidatesForFirst.size() > 1) { if (candidatesForFirst.size() > 1) {
// 有多个候选,通过history_top_100表的点系数排序 // 有多个候选,通过带回退的多级筛选
log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(), log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(),
candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())); candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()) candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
); );
@@ -765,7 +765,7 @@ public class BallAnalysisService {
// 处理第17个位置系数最大的球号的毛边情况 // 处理第17个位置系数最大的球号的毛边情况
if (candidates.size() == 17) { if (candidates.size() == 17) {
int lastIndex = 16; // 第17个位置的索引 int lastIndex = 0; // 第17个位置的索引
Double lastCoefficient = candidates.get(lastIndex).getCoefficient(); Double lastCoefficient = candidates.get(lastIndex).getCoefficient();
int actualLastIndex = startIndex + lastIndex; int actualLastIndex = startIndex + lastIndex;
@@ -930,6 +930,9 @@ public class BallAnalysisService {
Double firstCoefficient = t4List.get(startIndex).getLineCoefficient(); Double firstCoefficient = t4List.get(startIndex).getLineCoefficient();
List<T4> candidatesForFirst = new ArrayList<>(); List<T4> candidatesForFirst = new ArrayList<>();
// 先包含第一个位置本身
candidatesForFirst.add(t4List.get(startIndex));
// 找出第一个位置上面(如果存在)的所有相同线系数的记录 // 找出第一个位置上面(如果存在)的所有相同线系数的记录
for (int i = 0; i < startIndex; i++) { for (int i = 0; i < startIndex; i++) {
if (t4List.get(i).getLineCoefficient().equals(firstCoefficient)) { if (t4List.get(i).getLineCoefficient().equals(firstCoefficient)) {
@@ -937,12 +940,12 @@ public class BallAnalysisService {
} }
} }
if (candidatesForFirst.size() > 0) { if (candidatesForFirst.size() > 1) {
// 有多个候选,通过history_top_100表的点系数排序 // 有多个候选,通过带回退的多级筛选
log.debug("第一位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), log.debug("第一位上面及自身有{}个相同线系数的候选:{}", candidatesForFirst.size(),
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
); );
@@ -954,6 +957,9 @@ public class BallAnalysisService {
Double lastCoefficient = t4List.get(endIndex).getLineCoefficient(); Double lastCoefficient = t4List.get(endIndex).getLineCoefficient();
List<T4> candidatesForLast = new ArrayList<>(); List<T4> candidatesForLast = new ArrayList<>();
// 先包含最后一个位置本身
candidatesForLast.add(t4List.get(endIndex));
// 找出最后一个位置下面(如果存在)的所有相同线系数的记录 // 找出最后一个位置下面(如果存在)的所有相同线系数的记录
for (int i = endIndex + 1; i < t4List.size(); i++) { for (int i = endIndex + 1; i < t4List.size(); i++) {
if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) { if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) {
@@ -961,12 +967,12 @@ public class BallAnalysisService {
} }
} }
if (candidatesForLast.size() > 0) { if (candidatesForLast.size() > 1) {
// 有多个候选,通过history_top_100表的点系数排序 // 有多个候选,通过带回退的多级筛选
log.debug("最后一位下面有{}个相同线系数的候选:{}", candidatesForLast.size(), log.debug("最后一位下面及自身有{}个相同线系数的候选:{}", candidatesForLast.size(),
candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
); );
@@ -997,7 +1003,7 @@ public class BallAnalysisService {
// 处理第17个位置系数最大的球号的毛边情况 // 处理第17个位置系数最大的球号的毛边情况
if (candidates.size() == 17) { if (candidates.size() == 17) {
int lastIndex = 16; // 第17个位置的索引 int lastIndex = 0; // 第17个位置的索引
Double lastCoefficient = t4List.get(lastIndex).getLineCoefficient(); Double lastCoefficient = t4List.get(lastIndex).getLineCoefficient();
int actualLastIndex = lastIndex; int actualLastIndex = lastIndex;
@@ -1020,7 +1026,7 @@ public class BallAnalysisService {
log.debug("第17个位置及其向上有{}个相同线系数的候选:{}", candidatesFor17th.size(), log.debug("第17个位置及其向上有{}个相同线系数的候选:{}", candidatesFor17th.size(),
candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
); );
@@ -1058,7 +1064,7 @@ public class BallAnalysisService {
log.debug("第1位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(), log.debug("第1位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(),
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
); );
result.set(0, bestBall); result.set(0, bestBall);
@@ -1079,7 +1085,7 @@ public class BallAnalysisService {
log.debug("第10位下面有{}个相同线系数的候选:{}", candidatesForLast.size(), log.debug("第10位下面有{}个相同线系数的候选:{}", candidatesForLast.size(),
candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())); candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()) candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
); );
result.set(result.size() - 1, bestBall); result.set(result.size() - 1, bestBall);
@@ -1126,7 +1132,7 @@ public class BallAnalysisService {
log.debug("排行{}有{}个相同的球号:{}", current.getNo(), sameRankList.size(), log.debug("排行{}有{}个相同的球号:{}", current.getNo(), sameRankList.size(),
sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList())); sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList()) sameRankList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList())
); );
result.add(bestBall); result.add(bestBall);
@@ -1170,6 +1176,46 @@ public class BallAnalysisService {
return bestBall; 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个数字
* 如果频次相同的球号超过11个使用多层筛选 * 如果频次相同的球号超过11个使用多层筛选
@@ -1648,7 +1694,7 @@ public class BallAnalysisService {
result = getHighLevelBallsFromT7(t7List); result = getHighLevelBallsFromT7(t7List);
break; break;
case "M": case "M":
// 中位:取面系数平均值向上6个向下4个10个球号 // 中位:取面系数平均值向上5个、向下4个,共10个球号
result = getMiddleLevelBallsFromT7(t7List); result = getMiddleLevelBallsFromT7(t7List);
break; break;
case "L": case "L":
@@ -1708,7 +1754,11 @@ public class BallAnalysisService {
} }
/** /**
* 中位算法从T7表取面系数平均值向上5个向下5个的10个球号 * 中位算法从T7表取面系数平均值向上5个向下4个10个球号
* 毛边处理要求:
* - 向上边界包含边界本身(或其上方/下方的同系数项)一起参与比较;
* - 向下边界包含边界本身(或其下方的同系数项)一起参与比较;
* - 当边界存在多个相同面系数的候选时,按 history_top_100 → history_top → 随机 的顺序筛选。
*/ */
private List<Integer> getMiddleLevelBallsFromT7(List<T7> t7List) { private List<Integer> getMiddleLevelBallsFromT7(List<T7> t7List) {
if (t7List.size() < 10) { if (t7List.size() < 10) {
@@ -1747,7 +1797,7 @@ public class BallAnalysisService {
log.debug("找到大于平均值且最接近的位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient()); log.debug("找到大于平均值且最接近的位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient());
} }
// 向上5个向下5共10个 // 向上5个向下4共10个
int startIndex = Math.max(0, avgIndex - 5); int startIndex = Math.max(0, avgIndex - 5);
int endIndex = Math.min(t7List.size() - 1, avgIndex + 4); int endIndex = Math.min(t7List.size() - 1, avgIndex + 4);
@@ -1766,7 +1816,7 @@ public class BallAnalysisService {
candidates.add(t7List.get(i).getSlaveBallNumber()); candidates.add(t7List.get(i).getSlaveBallNumber());
} }
// 处理边界位置的相同面系数情况 // 处理边界位置的相同面系数情况(包含边界本身参与比较,并带回退规则)
List<Integer> result = handleT7BoundaryConflicts(t7List, candidates, startIndex, endIndex); List<Integer> result = handleT7BoundaryConflicts(t7List, candidates, startIndex, endIndex);
log.debug("T7中位算法选择范围[{}, {}],共{}个球", startIndex, endIndex, result.size()); log.debug("T7中位算法选择范围[{}, {}],共{}个球", startIndex, endIndex, result.size());
@@ -1782,9 +1832,9 @@ public class BallAnalysisService {
return t7List.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()); return t7List.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList());
} }
// 从最后13个开始取第3-13个即倒数第11到倒数第1个) // 从最后13个开始取第3-13个即倒数第12到倒数第2个)
int startIndex = Math.max(0, t7List.size() - 11); int startIndex = Math.max(0, t7List.size() - 12);
int endIndex = t7List.size() - 1; int endIndex = t7List.size() - 2;
List<Integer> candidates = new ArrayList<>(); List<Integer> candidates = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) { for (int i = startIndex; i <= endIndex; i++) {
@@ -1806,9 +1856,10 @@ public class BallAnalysisService {
List<Integer> result = new ArrayList<>(candidates); List<Integer> result = new ArrayList<>(candidates);
if (result.size() >= 10) { if (result.size() >= 10) {
// 检查第1个位置最大面系数的冲突 // 检查第1个位置最大面系数的冲突:包含边界本身
Double firstCoefficient = t7List.get(startIndex).getFaceCoefficient(); Double firstCoefficient = t7List.get(startIndex).getFaceCoefficient();
List<T7> candidatesForFirst = new ArrayList<>(); List<T7> candidatesForFirst = new ArrayList<>();
candidatesForFirst.add(t7List.get(startIndex)); // 边界本身
// 找出第一个位置上面(如果存在)的所有相同面系数的记录 // 找出第一个位置上面(如果存在)的所有相同面系数的记录
for (int i = 0; i < startIndex; i++) { for (int i = 0; i < startIndex; i++) {
@@ -1817,19 +1868,20 @@ public class BallAnalysisService {
} }
} }
if (candidatesForFirst.size() > 0) { if (candidatesForFirst.size() > 1) {
log.debug("第1位上面有{}个相同面系数的候选:{}", candidatesForFirst.size(), log.debug("第1位及其上面有{}个相同面系数的候选:{}", candidatesForFirst.size(),
candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()) candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())
); );
result.set(0, bestBall); result.set(0, bestBall);
} }
// 检查第10个位置最小面系数的冲突 // 检查第10个位置最小面系数的冲突:包含边界本身
Double lastCoefficient = t7List.get(endIndex).getFaceCoefficient(); Double lastCoefficient = t7List.get(endIndex).getFaceCoefficient();
List<T7> candidatesForLast = new ArrayList<>(); List<T7> candidatesForLast = new ArrayList<>();
candidatesForLast.add(t7List.get(endIndex)); // 边界本身
// 找出第10个位置下面如果存在的所有相同面系数的记录 // 找出第10个位置下面如果存在的所有相同面系数的记录
for (int i = endIndex + 1; i < t7List.size(); i++) { for (int i = endIndex + 1; i < t7List.size(); i++) {
@@ -1838,11 +1890,11 @@ public class BallAnalysisService {
} }
} }
if (candidatesForLast.size() > 0) { if (candidatesForLast.size() > 1) {
log.debug("第10位下面有{}个相同面系数的候选:{}", candidatesForLast.size(), log.debug("第10位及其下面有{}个相同面系数的候选:{}", candidatesForLast.size(),
candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())); candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()));
Integer bestBall = selectBestBallFromHistoryTop100( Integer bestBall = selectBestBallWithFallback(
candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()) candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())
); );
result.set(result.size() - 1, bestBall); result.set(result.size() - 1, bestBall);
@@ -2163,9 +2215,9 @@ public class BallAnalysisService {
result.addAll(balls); result.addAll(balls);
log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size());
} else { } else {
// 会超过8个需要用第一步记录的T7面系数进行二次筛选 // 会超过8个先按第一步T7面系数排序如仍有边界相同面系数继续按 100期 → 历史 → 随机 的回退规则,只在边界同系数的集合内决策
int remainingSlots = 8 - result.size(); int remainingSlots = 8 - result.size();
log.info("需要从{}个频率相同的球号中选择{}个,使用第一步T7面系数进行筛选找不到则按0处理", balls.size(), remainingSlots); log.info("需要从{}个频率相同的球号中选择{}个,先按第一步T7面系数进行筛选找不到则按0处理", balls.size(), remainingSlots);
// 构建 step1 球号->面系数 映射找不到则为0 // 构建 step1 球号->面系数 映射找不到则为0
Map<Integer, Double> num2Coeff = new HashMap<>(); Map<Integer, Double> num2Coeff = new HashMap<>();
@@ -2176,7 +2228,8 @@ public class BallAnalysisService {
} }
// 先按面系数降序,再按球号升序 // 先按面系数降序,再按球号升序
balls.sort((a, b) -> { List<Integer> sortedByT7 = new ArrayList<>(balls);
sortedByT7.sort((a, b) -> {
double ca = num2Coeff.getOrDefault(a, 0.0); double ca = num2Coeff.getOrDefault(a, 0.0);
double cb = num2Coeff.getOrDefault(b, 0.0); double cb = num2Coeff.getOrDefault(b, 0.0);
if (Double.compare(cb, ca) != 0) { if (Double.compare(cb, ca) != 0) {
@@ -2185,11 +2238,40 @@ public class BallAnalysisService {
return Integer.compare(a, b); return Integer.compare(a, b);
}); });
List<Integer> selectedBalls = balls.subList(0, Math.min(remainingSlots, balls.size())); // 如果正好能取齐,直接取
result.addAll(selectedBalls); if (sortedByT7.size() <= remainingSlots) {
result.addAll(sortedByT7);
log.info("按T7面系数后正好取齐选择{}", sortedByT7);
break;
}
log.info("通过第一步T7面系数筛选完成最终选择{}", selectedBalls); // 存在边界相同面系数:提取阈值、拆分高于阈值与阈值相等的集合
break; // 已经达到8个结束 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个结束 // 如果已经有8个结束
@@ -2513,6 +2595,90 @@ public class BallAnalysisService {
return 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 level 高位/中位/低位标识 (H/M/L)
@@ -2755,6 +2921,64 @@ public class BallAnalysisService {
return bestBall; 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个蓝球号码简化版本 * 从T8表获取指定红球对应的5个蓝球号码简化版本
* @param masterBallNumber 主球号(红球) * @param masterBallNumber 主球号(红球)
@@ -2907,7 +3131,7 @@ public class BallAnalysisService {
break; break;
} }
case "L": { case "L": {
// 低位取系数倒数第二个向上5个窗口顶部位置毛边:向上查找相同面系数,使用blue_history_top_100比较 // 低位取系数倒数第二个向上5个窗口顶部和底部位置均有毛边:使用blue_history_top_100/blue_history_top多级回退比较
if (size < 5) { if (size < 5) {
log.warn("T8表数据不足5条实际{}条", size); log.warn("T8表数据不足5条实际{}条", size);
return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()); return t8List.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList());
@@ -2915,11 +3139,13 @@ public class BallAnalysisService {
int endIndex = size - 2; // 倒数第二 int endIndex = size - 2; // 倒数第二
int startIndex = Math.max(0, endIndex - 4); int startIndex = Math.max(0, endIndex - 4);
// 先按基本规则选出5个位置的球号
List<Integer> baseResult = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) { for (int i = startIndex; i <= endIndex; i++) {
result.add(t8List.get(i).getSlaveBallNumber()); baseResult.add(t8List.get(i).getSlaveBallNumber());
} }
// 顶部位置(系数最大的)毛边处理:向上查找 // 处理顶部位置(系数最大的)毛边:向上查找相同面系数
Double topCoeff = t8List.get(startIndex).getFaceCoefficient(); Double topCoeff = t8List.get(startIndex).getFaceCoefficient();
List<T8> candidatesTop = new ArrayList<>(); List<T8> candidatesTop = new ArrayList<>();
candidatesTop.add(t8List.get(startIndex)); candidatesTop.add(t8List.get(startIndex));
@@ -2933,11 +3159,37 @@ public class BallAnalysisService {
if (candidatesTop.size() > 1) { if (candidatesTop.size() > 1) {
log.debug("低位顶部位置存在{}个相同面系数候选:{}", candidatesTop.size(), log.debug("低位顶部位置存在{}个相同面系数候选:{}", candidatesTop.size(),
candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList())); candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()));
Integer best = selectBestBallFromBlueHistoryTop100( List<Integer> topSelected = selectBlueBallsWithFallback(
candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()) candidatesTop.stream().map(T8::getSlaveBallNumber).collect(Collectors.toList()), 1
); );
result.set(0, best); if (!topSelected.isEmpty()) {
baseResult.set(0, topSelected.get(0));
} }
}
// 处理底部位置(系数最小的)毛边:向下查找相同面系数
Double bottomCoeff = t8List.get(endIndex).getFaceCoefficient();
List<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; break;
} }
} }

View File

@@ -5,7 +5,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cpzs url: jdbc:mysql://localhost:3306/cpzs
username: cpzs_root username: cpzs_root
password: cpzs_123456 password: cpzs_root
# datasource: # datasource:
# driver-class-name: com.mysql.cj.jdbc.Driver # 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 # 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