2025-08-01 19:09:57 +08:00
|
|
|
|
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;
|
2025-11-04 17:18:21 +08:00
|
|
|
|
import com.xy.xyaicpzs.domain.vo.BallAnalysisResultVO;
|
|
|
|
|
|
import com.xy.xyaicpzs.domain.vo.FollowBallAnalysisResultVO;
|
|
|
|
|
|
import com.xy.xyaicpzs.domain.vo.BlueBallAnalysisResultVO;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 球号分析服务类
|
|
|
|
|
|
* 实现复杂的球号分析算法
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
|
@Service
|
|
|
|
|
|
public class BallAnalysisService {
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 球号和系数的对应关系
|
|
|
|
|
|
*/
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
@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;
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 辅助:查询给定主球在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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 球号分析算法主方法
|
|
|
|
|
|
* @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<>();
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 第一步:记录球号和系数的对应关系
|
|
|
|
|
|
List<BallWithCoefficient> ballsWithCoefficients = new ArrayList<>();
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 第一步:处理6个红球,每个红球获取17个数字
|
|
|
|
|
|
log.info("第一步:处理6个红球,使用{}级别算法", level);
|
|
|
|
|
|
for (int i = 0; i < redBalls.size(); i++) {
|
|
|
|
|
|
Integer redBall = redBalls.get(i);
|
|
|
|
|
|
log.info("处理第{}个红球:{}", i + 1, redBall);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
List<BallWithCoefficient> ballsWithCoeffs = getTop17FromT3WithCoefficients(redBall, level);
|
|
|
|
|
|
ballsWithCoefficients.addAll(ballsWithCoeffs);
|
|
|
|
|
|
|
|
|
|
|
|
List<Integer> ballNumbers = ballsWithCoeffs.stream()
|
|
|
|
|
|
.map(BallWithCoefficient::getBallNumber)
|
|
|
|
|
|
.collect(Collectors.toList());
|
2025-08-01 19:09:57 +08:00
|
|
|
|
allNumbers.addAll(ballNumbers);
|
|
|
|
|
|
|
|
|
|
|
|
log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers);
|
|
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 记录第一步的球号和系数统计
|
|
|
|
|
|
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("=== 第一步统计结束 ===");
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 第二步:从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个
|
2025-08-07 19:53:50 +08:00
|
|
|
|
List<Integer> result = getTop11ByFrequency(allNumbers, ballsWithCoefficients);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
log.info("球号分析算法完成,结果:{}", result);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 球号分析算法(包含筛选过程说明)
|
|
|
|
|
|
* @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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 从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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 从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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 高位算法:从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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 获取前17个
|
2025-08-07 19:53:50 +08:00
|
|
|
|
List<BallWithCoefficient> result = new ArrayList<>();
|
2025-08-08 19:18:50 +08:00
|
|
|
|
for (int i = 0; i < 17 && i < t3List.size(); i++) {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
T3 t3 = t3List.get(i);
|
|
|
|
|
|
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 处理第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; // 系数不同就停止查找
|
|
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
if (candidatesFor17th.size() > 1) {
|
|
|
|
|
|
log.debug("第17个位置及其向下有{}个相同线系数的候选:{}", candidatesFor17th.size(),
|
|
|
|
|
|
candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 中位算法:从T3表取线系数平均值向上8个球,向下8个球,共17个
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个)
|
2025-08-01 19:09:57 +08:00
|
|
|
|
int avgIndex = -1;
|
|
|
|
|
|
double minDiff = Double.MAX_VALUE;
|
|
|
|
|
|
for (int i = 0; i < t3List.size(); i++) {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
double coefficient = t3List.get(i).getLineCoefficient();
|
|
|
|
|
|
// 只考虑大于平均值的值
|
|
|
|
|
|
if (coefficient > avgCoefficient) {
|
|
|
|
|
|
double diff = coefficient - avgCoefficient;
|
|
|
|
|
|
if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值
|
2025-08-01 19:09:57 +08:00
|
|
|
|
minDiff = diff;
|
|
|
|
|
|
avgIndex = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 如果没有找到大于平均值的,则取最大值
|
|
|
|
|
|
if (avgIndex == -1) {
|
|
|
|
|
|
avgIndex = 0; // T3表按线系数降序排列,第一个就是最大值
|
|
|
|
|
|
log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t3List.get(avgIndex).getLineCoefficient());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t3List.get(avgIndex).getLineCoefficient());
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 向上8个,向下8个,共17个
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 处理边界位置的相同线系数情况
|
|
|
|
|
|
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()));
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-07 19:53:50 +08:00
|
|
|
|
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()));
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-07 19:53:50 +08:00
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 处理边界毛边情况
|
|
|
|
|
|
if (result.size() >= 8) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 处理向上8个球号的第8个位置(对应区间起点 startIndex,即 result[0])的毛边情况
|
|
|
|
|
|
int upBoundaryIndex = 0; // 向上第8个球号在 result 中的索引
|
2025-08-08 19:18:50 +08:00
|
|
|
|
if (upBoundaryIndex < result.size()) {
|
|
|
|
|
|
Double upBoundaryCoefficient = result.get(upBoundaryIndex).getCoefficient();
|
|
|
|
|
|
|
|
|
|
|
|
// 查找向上还有没有相同系数值的球号
|
|
|
|
|
|
List<T3> upCandidates = new ArrayList<>();
|
2025-08-12 17:19:51 +08:00
|
|
|
|
int actualUpIndex = startIndex + upBoundaryIndex; // 即 startIndex
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加边界球号本身
|
|
|
|
|
|
upCandidates.add(t3List.get(actualUpIndex));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 向上查找相同系数值的球号(在 startIndex 之上)
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
log.debug("向上第8个球号(startIndex)及其上面有{}个相同线系数的候选:{}", upCandidates.size(),
|
2025-08-08 19:18:50 +08:00
|
|
|
|
upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-08 19:18:50 +08:00
|
|
|
|
upCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 找到对应的T3对象并替换 result[0]
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 处理向下8个球号的第8个位置(对应区间终点 endIndex,即 result[last])的毛边情况
|
|
|
|
|
|
int downBoundaryIndex = Math.min(16, result.size() - 1); // 向下第8个球号在 result 中的索引
|
2025-08-08 19:18:50 +08:00
|
|
|
|
if (downBoundaryIndex >= 8 && downBoundaryIndex < result.size()) {
|
|
|
|
|
|
Double downBoundaryCoefficient = result.get(downBoundaryIndex).getCoefficient();
|
|
|
|
|
|
|
|
|
|
|
|
// 查找向下还有没有相同系数值的球号
|
|
|
|
|
|
List<T3> downCandidates = new ArrayList<>();
|
2025-08-12 17:19:51 +08:00
|
|
|
|
int actualDownIndex = startIndex + downBoundaryIndex; // 即 endIndex
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加边界球号本身
|
|
|
|
|
|
downCandidates.add(t3List.get(actualDownIndex));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 向下查找相同系数值的球号(在 endIndex 之下)
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
log.debug("向下第8个球号(endIndex)及其下面有{}个相同线系数的候选:{}", downCandidates.size(),
|
2025-08-08 19:18:50 +08:00
|
|
|
|
downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-08 19:18:50 +08:00
|
|
|
|
downCandidates.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 找到对应的T3对象并替换 result[last]
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 有多个候选,通过带回退的多级筛选
|
2025-08-01 19:09:57 +08:00
|
|
|
|
log.debug("第一位有{}个相同线系数的候选:{}", candidatesForFirst.size(),
|
|
|
|
|
|
candidatesForFirst.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 低位算法:从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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 处理第17个位置(系数最大的球号)的毛边情况
|
|
|
|
|
|
if (candidates.size() == 17) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
int lastIndex = 0; // 第17个位置的索引
|
2025-08-08 19:18:50 +08:00
|
|
|
|
Double lastCoefficient = candidates.get(lastIndex).getCoefficient();
|
|
|
|
|
|
int actualLastIndex = startIndex + lastIndex;
|
2025-08-07 19:53:50 +08:00
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 查找第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; // 系数不同就停止查找
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
if (candidatesFor17th.size() > 1) {
|
|
|
|
|
|
log.debug("第17个位置及其向上有{}个相同线系数的候选:{}", candidatesFor17th.size(),
|
|
|
|
|
|
candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList()));
|
2025-08-07 19:53:50 +08:00
|
|
|
|
|
|
|
|
|
|
Integer bestBall = selectBestBallFromHistoryTop100(
|
2025-08-08 19:18:50 +08:00
|
|
|
|
candidatesFor17th.stream().map(T3::getSlaveBallNumber).collect(Collectors.toList())
|
2025-08-07 19:53:50 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 找到对应的T3对象并替换第17个位置
|
|
|
|
|
|
T3 selectedT3 = candidatesFor17th.stream()
|
2025-08-07 19:53:50 +08:00
|
|
|
|
.filter(t3 -> t3.getSlaveBallNumber().equals(bestBall))
|
|
|
|
|
|
.findFirst()
|
2025-08-08 19:18:50 +08:00
|
|
|
|
.orElse(candidatesFor17th.get(0));
|
|
|
|
|
|
candidates.set(lastIndex, new BallWithCoefficient(selectedT3.getSlaveBallNumber(), selectedT3.getLineCoefficient(), masterBallNumber));
|
|
|
|
|
|
|
|
|
|
|
|
log.debug("第17个位置毛边处理完成,最终选择:{}", bestBall);
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.debug("低位算法选择范围:[{}, {}],共{}个球", startIndex, t3List.size() - 1, candidates.size());
|
|
|
|
|
|
return candidates.subList(0, Math.min(17, candidates.size()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 高位算法:从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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 中位算法:从T4表取线系数平均值向上8个球,向下8个球,共17个
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个)
|
2025-08-01 19:09:57 +08:00
|
|
|
|
int avgIndex = -1;
|
|
|
|
|
|
double minDiff = Double.MAX_VALUE;
|
|
|
|
|
|
for (int i = 0; i < t4List.size(); i++) {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
double coefficient = t4List.get(i).getLineCoefficient();
|
|
|
|
|
|
// 只考虑大于平均值的值
|
|
|
|
|
|
if (coefficient > avgCoefficient) {
|
|
|
|
|
|
double diff = coefficient - avgCoefficient;
|
|
|
|
|
|
if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值
|
2025-08-01 19:09:57 +08:00
|
|
|
|
minDiff = diff;
|
|
|
|
|
|
avgIndex = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 如果没有找到大于平均值的,则取最大值
|
|
|
|
|
|
if (avgIndex == -1) {
|
|
|
|
|
|
avgIndex = 0; // T4表按线系数降序排列,第一个就是最大值
|
|
|
|
|
|
log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient());
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 向上8个,向下8个,共17个
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 处理边界位置的相同线系数情况
|
|
|
|
|
|
result = handleT4BoundaryConflicts(t4List, result, startIndex, endIndex);
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
log.debug("中位算法选择范围:[{}, {}],共{}个球", startIndex, endIndex, result.size());
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理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<>();
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 先包含第一个位置本身
|
|
|
|
|
|
candidatesForFirst.add(t4List.get(startIndex));
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找出第一个位置上面(如果存在)的所有相同线系数的记录
|
|
|
|
|
|
for (int i = 0; i < startIndex; i++) {
|
|
|
|
|
|
if (t4List.get(i).getLineCoefficient().equals(firstCoefficient)) {
|
|
|
|
|
|
candidatesForFirst.add(t4List.get(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
if (candidatesForFirst.size() > 1) {
|
|
|
|
|
|
// 有多个候选,通过带回退的多级筛选
|
|
|
|
|
|
log.debug("第一位上面及自身有{}个相同线系数的候选:{}", candidatesForFirst.size(),
|
2025-08-07 19:53:50 +08:00
|
|
|
|
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-07 19:53:50 +08:00
|
|
|
|
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 替换第一个位置
|
|
|
|
|
|
result.set(0, bestBall);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查最后一个位置(最小线系数)的冲突
|
|
|
|
|
|
Double lastCoefficient = t4List.get(endIndex).getLineCoefficient();
|
|
|
|
|
|
List<T4> candidatesForLast = new ArrayList<>();
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 先包含最后一个位置本身
|
|
|
|
|
|
candidatesForLast.add(t4List.get(endIndex));
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找出最后一个位置下面(如果存在)的所有相同线系数的记录
|
|
|
|
|
|
for (int i = endIndex + 1; i < t4List.size(); i++) {
|
|
|
|
|
|
if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) {
|
|
|
|
|
|
candidatesForLast.add(t4List.get(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
if (candidatesForLast.size() > 1) {
|
|
|
|
|
|
// 有多个候选,通过带回退的多级筛选
|
|
|
|
|
|
log.debug("最后一位下面及自身有{}个相同线系数的候选:{}", candidatesForLast.size(),
|
2025-08-07 19:53:50 +08:00
|
|
|
|
candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-07 19:53:50 +08:00
|
|
|
|
candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 替换最后一个位置
|
|
|
|
|
|
result.set(result.size() - 1, bestBall);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
2025-08-08 19:18:50 +08:00
|
|
|
|
* 低位算法:从T4表取最小值向上17个球,含最小值,共17个球号
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
private List<Integer> getLowLevelBallsFromT4(List<T4> t4List) {
|
2025-08-08 19:18:50 +08:00
|
|
|
|
if (t4List.size() < 17) {
|
|
|
|
|
|
log.warn("T4表数据不足17条,实际{}条", t4List.size());
|
2025-08-01 19:09:57 +08:00
|
|
|
|
return t4List.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 从最后17个开始(最小的线系数)
|
|
|
|
|
|
int startIndex = Math.max(0, t4List.size() - 17);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
List<Integer> candidates = new ArrayList<>();
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
for (int i = startIndex; i < t4List.size(); i++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
candidates.add(t4List.get(i).getSlaveBallNumber());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 处理第17个位置(系数最大的球号)的毛边情况
|
|
|
|
|
|
if (candidates.size() == 17) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
int lastIndex = 0; // 第17个位置的索引
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-08 19:18:50 +08:00
|
|
|
|
candidatesFor17th.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 替换第17个位置的球号
|
|
|
|
|
|
candidates.set(lastIndex, bestBall);
|
|
|
|
|
|
|
|
|
|
|
|
log.debug("第17个位置毛边处理完成,最终选择:{}", bestBall);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
log.debug("T4低位算法选择范围:[{}, {}],共{}个球", startIndex, t4List.size() - 1, candidates.size());
|
|
|
|
|
|
return candidates.subList(0, Math.min(17, candidates.size()));
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理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<>();
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找出第一个位置上面(如果存在)的所有相同线系数的记录
|
|
|
|
|
|
for (int i = 0; i < startIndex; i++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
if (t4List.get(i).getLineCoefficient().equals(firstCoefficient)) {
|
|
|
|
|
|
candidatesForFirst.add(t4List.get(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
if (candidatesForFirst.size() > 0) {
|
|
|
|
|
|
log.debug("第1位上面有{}个相同线系数的候选:{}", candidatesForFirst.size(),
|
2025-08-01 19:09:57 +08:00
|
|
|
|
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-01 19:09:57 +08:00
|
|
|
|
candidatesForFirst.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList())
|
|
|
|
|
|
);
|
|
|
|
|
|
result.set(0, bestBall);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查第10个位置(最小线系数)的冲突
|
|
|
|
|
|
Double lastCoefficient = t4List.get(endIndex).getLineCoefficient();
|
|
|
|
|
|
List<T4> candidatesForLast = new ArrayList<>();
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找出第10个位置下面(如果存在)的所有相同线系数的记录
|
|
|
|
|
|
for (int i = endIndex + 1; i < t4List.size(); i++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
if (t4List.get(i).getLineCoefficient().equals(lastCoefficient)) {
|
|
|
|
|
|
candidatesForLast.add(t4List.get(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
if (candidatesForLast.size() > 0) {
|
|
|
|
|
|
log.debug("第10位下面有{}个相同线系数的候选:{}", candidatesForLast.size(),
|
2025-08-01 19:09:57 +08:00
|
|
|
|
candidatesForLast.stream().map(T4::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 在候选球号中进行多级回退选择:
|
|
|
|
|
|
* 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 统计数字出现频率,返回频率最高的前11个数字
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 如果频次相同的球号超过11个,使用多层筛选:
|
|
|
|
|
|
* 1. ballNumbersWithCoefficients系数和筛选
|
|
|
|
|
|
* 2. history_top_100表排名筛选
|
|
|
|
|
|
* 3. history_top表点系数筛选
|
|
|
|
|
|
* 4. 随机选择
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-08-07 19:53:50 +08:00
|
|
|
|
private List<Integer> getTop11ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> ballsWithCoefficients) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 打印所有球号出现频率的详细信息
|
|
|
|
|
|
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("=== 频率统计结束 ===");
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
// 按频率分组
|
|
|
|
|
|
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 {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 会超过11个,需要从这组球号中选择部分
|
2025-08-01 19:09:57 +08:00
|
|
|
|
int remainingSlots = 11 - result.size();
|
2025-08-07 19:53:50 +08:00
|
|
|
|
log.info("需要从{}个频率相同的球号中选择{}个,开始多层筛选", balls.size(), remainingSlots);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
List<Integer> selectedBalls = selectBallsByMultiLevelFiltering(balls, remainingSlots, ballsWithCoefficients);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
result.addAll(selectedBalls);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
log.info("多层筛选完成,最终选择:{}", selectedBalls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 统计数字出现频率,返回频率最高的前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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 多层筛选:ballNumbersWithCoefficients系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-08-07 19:53:50 +08:00
|
|
|
|
private List<Integer> selectBallsByMultiLevelFiltering(List<Integer> candidateBalls, int selectCount, List<BallWithCoefficient> ballsWithCoefficients) {
|
|
|
|
|
|
log.debug("开始多层筛选,候选球号:{},需要选择:{}个", candidateBalls, selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
if (candidateBalls.size() <= selectCount) {
|
|
|
|
|
|
return new ArrayList<>(candidateBalls);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
List<Integer> currentCandidates = new ArrayList<>(candidateBalls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 第一层: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);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 第二层:history_top_100表排名筛选
|
|
|
|
|
|
log.debug("=== 第二层:history_top_100表排名筛选 ===");
|
|
|
|
|
|
List<Integer> historyTop100Filtered = selectBallsByHistoryTop100Ranking(currentCandidates, selectCount);
|
|
|
|
|
|
log.debug("history_top_100表排名筛选结果:{}", historyTop100Filtered);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
if (historyTop100Filtered.size() == selectCount) {
|
|
|
|
|
|
log.debug("history_top_100表排名筛选完成,选择:{}", historyTop100Filtered);
|
|
|
|
|
|
return historyTop100Filtered;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
currentCandidates = historyTop100Filtered;
|
|
|
|
|
|
log.debug("history_top_100表筛选后剩余候选:{}", currentCandidates);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 第三层:history_top表点系数筛选
|
|
|
|
|
|
log.debug("=== 第三层:history_top表点系数筛选 ===");
|
|
|
|
|
|
List<Integer> historyTopFiltered = selectBallsByHistoryTopPointCoefficient(currentCandidates, selectCount);
|
|
|
|
|
|
log.debug("history_top表点系数筛选结果:{}", historyTopFiltered);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
if (historyTopFiltered.size() == selectCount) {
|
|
|
|
|
|
log.debug("history_top表点系数筛选完成,选择:{}", historyTopFiltered);
|
|
|
|
|
|
return historyTopFiltered;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
currentCandidates = historyTopFiltered;
|
|
|
|
|
|
log.debug("history_top表筛选后剩余候选:{}", currentCandidates);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 第四层:随机选择
|
|
|
|
|
|
log.debug("=== 第四层:随机选择 ===");
|
|
|
|
|
|
List<Integer> randomSelected = selectBallsRandomly(currentCandidates, selectCount);
|
|
|
|
|
|
log.debug("随机筛选完成,选择:{}", randomSelected);
|
|
|
|
|
|
return randomSelected;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-04 17:18:21 +08:00
|
|
|
|
* 多层筛选(包含过程说明):ballNumbersWithCoefficients系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-11-04 17:18:21 +08:00
|
|
|
|
private List<Integer> selectBallsByMultiLevelFilteringWithProcess(List<Integer> candidateBalls, int selectCount,
|
|
|
|
|
|
List<BallWithCoefficient> ballsWithCoefficients, StringBuilder processDescription) {
|
|
|
|
|
|
log.debug("开始多层筛选(含过程说明),候选球号:{},需要选择:{}个", candidateBalls, selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
if (candidateBalls.size() <= selectCount) {
|
|
|
|
|
|
return new ArrayList<>(candidateBalls);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
List<Integer> currentCandidates = new ArrayList<>(candidateBalls);
|
|
|
|
|
|
List<String> filteringSteps = new ArrayList<>();
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 第一层: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;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
2025-11-04 17:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果ballNumbersWithCoefficients筛选后的结果数量超过selectCount,说明有系数和相同的情况,继续下一层筛选
|
|
|
|
|
|
currentCandidates = ballNumbersFiltered;
|
|
|
|
|
|
log.debug("ballNumbersWithCoefficients筛选后剩余候选:{}", currentCandidates);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 第二层: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);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 第三层:history_top表点系数筛选
|
|
|
|
|
|
log.debug("=== 第三层:history_top表点系数筛选 ===");
|
|
|
|
|
|
List<Integer> historyTopFiltered = selectBallsByHistoryTopPointCoefficient(currentCandidates, selectCount);
|
|
|
|
|
|
log.debug("history_top表点系数筛选结果:{}", historyTopFiltered);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
if (historyTopFiltered.size() < currentCandidates.size()) {
|
|
|
|
|
|
filteringSteps.add("history_top表点系数筛选");
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
2025-11-04 17:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
if (historyTopFiltered.size() == selectCount) {
|
|
|
|
|
|
log.debug("history_top表点系数筛选完成,选择:{}", historyTopFiltered);
|
|
|
|
|
|
return historyTopFiltered;
|
|
|
|
|
|
}
|
|
|
|
|
|
currentCandidates = historyTopFiltered;
|
|
|
|
|
|
log.debug("history_top表筛选后剩余候选:{}", currentCandidates);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 第四层:随机选择
|
|
|
|
|
|
log.debug("=== 第四层:随机选择 ===");
|
|
|
|
|
|
List<Integer> randomSelected = selectBallsRandomly(currentCandidates, selectCount);
|
|
|
|
|
|
log.debug("随机筛选完成,选择:{}", randomSelected);
|
|
|
|
|
|
|
|
|
|
|
|
if (currentCandidates.size() > selectCount) {
|
|
|
|
|
|
filteringSteps.add("随机选择");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return randomSelected;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-04 17:18:21 +08:00
|
|
|
|
* 筛选详情结果内部类
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-11-04 17:18:21 +08:00
|
|
|
|
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;
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-04 17:18:21 +08:00
|
|
|
|
* 多层筛选结果内部类
|
2025-08-07 19:53:50 +08:00
|
|
|
|
*/
|
2025-11-04 17:18:21 +08:00
|
|
|
|
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) {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
log.debug("使用T3表线系数之和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
if (candidateBalls.size() <= selectCount) {
|
|
|
|
|
|
return new ArrayList<>(candidateBalls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 计算每个球号作为主球号时的线系数之和
|
|
|
|
|
|
Map<Integer, Double> ballCoefficientSum = new HashMap<>();
|
|
|
|
|
|
for (Integer ballNumber : candidateBalls) {
|
|
|
|
|
|
double coefficientSum = calculateT3LineCoefficientSum(ballNumber);
|
|
|
|
|
|
ballCoefficientSum.put(ballNumber, coefficientSum);
|
|
|
|
|
|
log.debug("球号{}的T3线系数之和:{}", ballNumber, coefficientSum);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 按线系数之和降序排序
|
|
|
|
|
|
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);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
List<Integer> result = new ArrayList<>();
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 按线系数之和从高到低处理
|
|
|
|
|
|
for (Map.Entry<Double, List<Integer>> group : coefficientGroups.entrySet()) {
|
|
|
|
|
|
Double coefficientSum = group.getKey();
|
2025-08-01 19:09:57 +08:00
|
|
|
|
List<Integer> balls = group.getValue();
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
log.debug("线系数之和{}的球号:{}", coefficientSum, balls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查加入这组球号后是否会超过selectCount个
|
|
|
|
|
|
if (result.size() + balls.size() <= selectCount) {
|
|
|
|
|
|
// 不会超过,直接添加所有球号(按球号升序排序)
|
|
|
|
|
|
Collections.sort(balls);
|
|
|
|
|
|
result.addAll(balls);
|
2025-08-07 19:53:50 +08:00
|
|
|
|
log.debug("直接添加{}个球号,当前总数:{}", balls.size(), result.size());
|
2025-08-01 19:09:57 +08:00
|
|
|
|
} else {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 会超过,返回所有相同线系数之和的球号,让下一层筛选来处理
|
|
|
|
|
|
result.addAll(balls);
|
|
|
|
|
|
log.debug("线系数之和相同,返回所有相同线系数的球号:{},让下一层筛选处理", balls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经有selectCount个,结束
|
|
|
|
|
|
if (result.size() >= selectCount) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
log.debug("T3表线系数筛选结果:{}", result);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 计算指定球号作为主球号时的T3表线系数之和
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-08-07 19:53:50 +08:00
|
|
|
|
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;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 跟随球号分析算法
|
|
|
|
|
|
* @param level 高位/中位/低位标识 (H/M/L)
|
|
|
|
|
|
* @param firstThreeRedBalls 前3个红球号码(独立的)
|
|
|
|
|
|
* @param lastSixRedBalls 后6个红球号码(独立的)
|
|
|
|
|
|
* @param blueBall 1个蓝球号码
|
2025-11-04 17:18:21 +08:00
|
|
|
|
* @return 分析结果:出现频率最高的前10位数字
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
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<>();
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 第一步:处理第1个红球,从T7表获取10个数字(同时记录系数)
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 同步记录上述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("=== 第一步统计结束 ===");
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
// 第二步:处理后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);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 第五步:用上期蓝球从T4表获取26个蓝球号码
|
|
|
|
|
|
log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", blueBall);
|
|
|
|
|
|
List<Integer> blueNumbers = getTop26FromT4(blueBall);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
allNumbers.addAll(blueNumbers);
|
|
|
|
|
|
log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers);
|
|
|
|
|
|
|
|
|
|
|
|
log.info("总共收集到{}个数字", allNumbers.size());
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 第六步:统计频率并获取前10个(若同频超过10个,用第一步T7面系数作为二次筛选依据)
|
|
|
|
|
|
List<Integer> result = getTop10ByFrequency(allNumbers, step1BallsWithCoefficients);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
log.info("跟随球号分析算法完成,结果:{}", result);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 跟随球号分析算法(带筛选过程说明)
|
|
|
|
|
|
* @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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 验证球号的有效性和唯一性
|
|
|
|
|
|
*/
|
|
|
|
|
|
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":
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 中位:取面系数平均值向上5个、向下4个,共10个球号
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-12 17:19:51 +08:00
|
|
|
|
* 中位算法:从T7表取面系数平均值向上5个、向下4个,共10个球号。
|
|
|
|
|
|
* 毛边处理要求:
|
|
|
|
|
|
* - 向上边界包含边界本身(或其上方/下方的同系数项)一起参与比较;
|
|
|
|
|
|
* - 向下边界包含边界本身(或其下方的同系数项)一起参与比较;
|
|
|
|
|
|
* - 当边界存在多个相同面系数的候选时,按 history_top_100 → history_top → 随机 的顺序筛选。
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个)
|
|
|
|
|
|
int avgIndex = -1;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
double minDiff = Double.MAX_VALUE;
|
|
|
|
|
|
for (int i = 0; i < t7List.size(); i++) {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
double coefficient = t7List.get(i).getFaceCoefficient();
|
|
|
|
|
|
// 只考虑大于平均值的值
|
|
|
|
|
|
if (coefficient > avgCoefficient) {
|
|
|
|
|
|
double diff = coefficient - avgCoefficient;
|
|
|
|
|
|
if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值
|
2025-08-01 19:09:57 +08:00
|
|
|
|
minDiff = diff;
|
|
|
|
|
|
avgIndex = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 如果没有找到大于平均值的,则取最大值
|
|
|
|
|
|
if (avgIndex == -1) {
|
|
|
|
|
|
avgIndex = 0; // T7表按面系数降序排列,第一个就是最大值
|
|
|
|
|
|
log.debug("未找到大于平均值的位置,使用最大值位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.debug("找到大于平均值且最接近的位置:{},面系数:{}", avgIndex, t7List.get(avgIndex).getFaceCoefficient());
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 向上5个,向下4个,共10个
|
2025-08-07 19:53:50 +08:00
|
|
|
|
int startIndex = Math.max(0, avgIndex - 5);
|
|
|
|
|
|
int endIndex = Math.min(t7List.size() - 1, avgIndex + 4);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 确保总共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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 处理边界位置的相同面系数情况(包含边界本身参与比较,并带回退规则)
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 从最后13个开始,取第3-13个(即倒数第12到倒数第2个)
|
|
|
|
|
|
int startIndex = Math.max(0, t7List.size() - 12);
|
|
|
|
|
|
int endIndex = t7List.size() - 2;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
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) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 检查第1个位置(最大面系数)的冲突:包含边界本身
|
2025-08-01 19:09:57 +08:00
|
|
|
|
Double firstCoefficient = t7List.get(startIndex).getFaceCoefficient();
|
|
|
|
|
|
List<T7> candidatesForFirst = new ArrayList<>();
|
2025-08-12 17:19:51 +08:00
|
|
|
|
candidatesForFirst.add(t7List.get(startIndex)); // 边界本身
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找出第一个位置上面(如果存在)的所有相同面系数的记录
|
|
|
|
|
|
for (int i = 0; i < startIndex; i++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
if (t7List.get(i).getFaceCoefficient().equals(firstCoefficient)) {
|
|
|
|
|
|
candidatesForFirst.add(t7List.get(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
if (candidatesForFirst.size() > 1) {
|
|
|
|
|
|
log.debug("第1位及其上面有{}个相同面系数的候选:{}", candidatesForFirst.size(),
|
2025-08-01 19:09:57 +08:00
|
|
|
|
candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-01 19:09:57 +08:00
|
|
|
|
candidatesForFirst.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList())
|
|
|
|
|
|
);
|
|
|
|
|
|
result.set(0, bestBall);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 检查第10个位置(最小面系数)的冲突:包含边界本身
|
2025-08-01 19:09:57 +08:00
|
|
|
|
Double lastCoefficient = t7List.get(endIndex).getFaceCoefficient();
|
|
|
|
|
|
List<T7> candidatesForLast = new ArrayList<>();
|
2025-08-12 17:19:51 +08:00
|
|
|
|
candidatesForLast.add(t7List.get(endIndex)); // 边界本身
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找出第10个位置下面(如果存在)的所有相同面系数的记录
|
|
|
|
|
|
for (int i = endIndex + 1; i < t7List.size(); i++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
if (t7List.get(i).getFaceCoefficient().equals(lastCoefficient)) {
|
|
|
|
|
|
candidatesForLast.add(t7List.get(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
if (candidatesForLast.size() > 1) {
|
|
|
|
|
|
log.debug("第10位及其下面有{}个相同面系数的候选:{}", candidatesForLast.size(),
|
2025-08-01 19:09:57 +08:00
|
|
|
|
candidatesForLast.stream().map(T7::getSlaveBallNumber).collect(Collectors.toList()));
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
Integer bestBall = selectBestBallWithFallback(
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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")
|
2025-08-07 19:53:50 +08:00
|
|
|
|
.orderByAsc("ballNumber") // 点系数相同时按球号升序
|
|
|
|
|
|
.last("LIMIT 3"); // 限制只返回前3条记录
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 中位算法:从T4表取线系数平均值向上5个向下5个的10个球号
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 找到大于平均值且最接近的位置(如果有多个相同值,选择最后一个)
|
|
|
|
|
|
int avgIndex = -1;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
double minDiff = Double.MAX_VALUE;
|
|
|
|
|
|
for (int i = 0; i < t4List.size(); i++) {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
double coefficient = t4List.get(i).getLineCoefficient();
|
|
|
|
|
|
// 只考虑大于平均值的值
|
|
|
|
|
|
if (coefficient > avgCoefficient) {
|
|
|
|
|
|
double diff = coefficient - avgCoefficient;
|
|
|
|
|
|
if (diff <= minDiff) { // 使用 <= 来选择最后一个相同值
|
2025-08-01 19:09:57 +08:00
|
|
|
|
minDiff = diff;
|
|
|
|
|
|
avgIndex = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-07 19:53:50 +08:00
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 如果没有找到大于平均值的,则取最大值
|
|
|
|
|
|
if (avgIndex == -1) {
|
|
|
|
|
|
avgIndex = 0; // T4表按线系数降序排列,第一个就是最大值
|
|
|
|
|
|
log.debug("未找到大于平均值的位置,使用最大值位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.debug("找到大于平均值且最接近的位置:{},线系数:{}", avgIndex, t4List.get(avgIndex).getLineCoefficient());
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 向上5个,向下5个,共10个
|
|
|
|
|
|
int startIndex = Math.max(0, avgIndex - 5);
|
|
|
|
|
|
int endIndex = Math.min(t4List.size() - 1, avgIndex + 4);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 确保总共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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-04 17:18:21 +08:00
|
|
|
|
* 统计数字出现频率,返回频率最高的前10个数字
|
|
|
|
|
|
* 如果频次相同的球号超过10个,使用T7表面系数进行二次筛选
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-11-04 17:18:21 +08:00
|
|
|
|
private List<Integer> getTop10ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> step1BallsWithCoefficients) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 检查加入这组球号后是否会超过10个
|
|
|
|
|
|
if (result.size() + balls.size() <= 10) {
|
|
|
|
|
|
// 不会超过10个,直接添加所有球号
|
2025-08-01 19:09:57 +08:00
|
|
|
|
result.addAll(balls);
|
|
|
|
|
|
log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size());
|
|
|
|
|
|
} else {
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 会超过10个:先按第一步T7面系数排序;如仍有边界相同面系数,继续按 100期 → 历史 → 随机 的回退规则,只在边界同系数的集合内决策
|
|
|
|
|
|
int remainingSlots = 10 - result.size();
|
2025-08-12 17:19:51 +08:00
|
|
|
|
log.info("需要从{}个频率相同的球号中选择{}个,先按第一步T7面系数进行筛选(找不到则按0处理)", balls.size(), remainingSlots);
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 构建 step1 球号->面系数 映射(找不到则为0)
|
|
|
|
|
|
Map<Integer, Double> num2Coeff = new HashMap<>();
|
|
|
|
|
|
if (step1BallsWithCoefficients != null) {
|
|
|
|
|
|
for (BallWithCoefficient bwc : step1BallsWithCoefficients) {
|
|
|
|
|
|
num2Coeff.put(bwc.getBallNumber(), bwc.getCoefficient());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 先按面系数降序,再按球号升序
|
2025-08-12 17:19:51 +08:00
|
|
|
|
List<Integer> sortedByT7 = new ArrayList<>(balls);
|
|
|
|
|
|
sortedByT7.sort((a, b) -> {
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 如果正好能取齐,直接取
|
|
|
|
|
|
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;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经有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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 统计频率并获取前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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 根据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表的排名进行筛选
|
2025-08-07 19:53:50 +08:00
|
|
|
|
List<Integer> filteredByTop100 = selectBallsByHistoryTop100Ranking(candidateBalls, selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
if (filteredByTop100.size() == selectCount) {
|
|
|
|
|
|
log.info("通过history_top_100表筛选成功,结果:{}", filteredByTop100);
|
|
|
|
|
|
return filteredByTop100;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第三级:使用history_top表的点系数进行筛选
|
|
|
|
|
|
log.info("history_top_100表筛选后仍有{}个球号,继续使用history_top表筛选", filteredByTop100.size());
|
2025-08-07 19:53:50 +08:00
|
|
|
|
List<Integer> filteredByTop = selectBallsByHistoryTopPointCoefficient(filteredByTop100, selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
if (filteredByTop.size() == selectCount) {
|
|
|
|
|
|
log.info("通过history_top表筛选成功,结果:{}", filteredByTop);
|
|
|
|
|
|
return filteredByTop;
|
|
|
|
|
|
}
|
2025-11-04 17:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 第四级:随机选择
|
|
|
|
|
|
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;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-04 17:18:21 +08:00
|
|
|
|
* 根据history_top_100表的排名筛选球号(包含详细信息)
|
2025-08-01 19:09:57 +08:00
|
|
|
|
* @param candidateBalls 候选球号列表
|
|
|
|
|
|
* @param selectCount 需要选择的数量
|
2025-11-04 17:18:21 +08:00
|
|
|
|
* @return 筛选结果和详细信息
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-11-04 17:18:21 +08:00
|
|
|
|
private FilteringDetailResult selectBallsByHistoryTop100RankingWithDetails(List<Integer> candidateBalls, int selectCount) {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
log.info("使用history_top_100表排名筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 查询这些球号在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);
|
2025-11-04 17:18:21 +08:00
|
|
|
|
return new FilteringDetailResult(candidateBalls.subList(0, Math.min(selectCount, candidateBalls.size())), "");
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按点系数分组
|
|
|
|
|
|
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<>();
|
2025-11-04 17:18:21 +08:00
|
|
|
|
StringBuilder detailInfo = new StringBuilder();
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
// 先记录所有球号的排位详细信息
|
|
|
|
|
|
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(")");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 再按点系数从高到低筛选球号
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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 {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 会超过,返回所有相同点系数的球号,让下一层筛选来处理
|
|
|
|
|
|
result.addAll(balls);
|
|
|
|
|
|
log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经有selectCount个,结束
|
|
|
|
|
|
if (result.size() >= selectCount) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理不在history_top_100表中的球号
|
|
|
|
|
|
List<Integer> notInTop100 = new ArrayList<>(candidateBalls);
|
2025-11-04 17:18:21 +08:00
|
|
|
|
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("(无排位)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 再选择需要的球号加入结果
|
2025-08-01 19:09:57 +08:00
|
|
|
|
if (!notInTop100.isEmpty() && result.size() < selectCount) {
|
|
|
|
|
|
int remainingSlots = selectCount - result.size();
|
|
|
|
|
|
int addCount = Math.min(remainingSlots, notInTop100.size());
|
|
|
|
|
|
result.addAll(notInTop100.subList(0, addCount));
|
2025-11-04 17:18:21 +08:00
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
log.info("添加不在history_top_100表中的{}个球号:{}", addCount, notInTop100.subList(0, addCount));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.info("history_top_100表筛选结果:{}", result);
|
2025-11-04 17:18:21 +08:00
|
|
|
|
return new FilteringDetailResult(result, detailInfo.toString());
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 根据history_top表的点系数筛选球号
|
|
|
|
|
|
* 当点系数相同时,返回所有相同点系数的球号,让下一层筛选来处理
|
2025-08-01 19:09:57 +08:00
|
|
|
|
*/
|
2025-08-07 19:53:50 +08:00
|
|
|
|
private List<Integer> selectBallsByHistoryTopPointCoefficient(List<Integer> candidateBalls, int selectCount) {
|
|
|
|
|
|
log.info("使用history_top表点系数筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 查询这些球号在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 {
|
2025-08-07 19:53:50 +08:00
|
|
|
|
// 会超过,返回所有相同点系数的球号,让下一层筛选来处理
|
|
|
|
|
|
result.addAll(balls);
|
|
|
|
|
|
log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 根据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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
2025-08-07 19:53:50 +08:00
|
|
|
|
* 随机选择球号
|
2025-08-01 19:09:57 +08:00
|
|
|
|
* @param candidateBalls 候选球号列表
|
|
|
|
|
|
* @param selectCount 需要选择的数量
|
|
|
|
|
|
* @return 随机选择的球号列表
|
|
|
|
|
|
*/
|
2025-08-07 19:53:50 +08:00
|
|
|
|
private List<Integer> selectBallsRandomly(List<Integer> candidateBalls, int selectCount) {
|
|
|
|
|
|
log.info("随机选择球号,候选:{},需要选择:{}", candidateBalls, selectCount);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 仅在一组同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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 蓝球分析算法主方法
|
|
|
|
|
|
* @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);
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
List<Integer> ballNumbers = getSimpleTop5FromT8ByLevel(redBall, level);
|
|
|
|
|
|
allNumbers.addAll(ballNumbers);
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 额外记录这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("=== 记录结束 ===");
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 第五步:用上期蓝球从T5表获取12个蓝球号码(含毛边处理)
|
|
|
|
|
|
log.info("第五步:用上期蓝球{}从T5表获取12个蓝球号码", lastBlueBall);
|
|
|
|
|
|
List<Integer> blueNumbers = getTop12FromT5(lastBlueBall);
|
2025-08-07 19:53:50 +08:00
|
|
|
|
allNumbers.addAll(blueNumbers);
|
|
|
|
|
|
log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
log.info("总共收集到{}个蓝球号码", allNumbers.size());
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 汇总第二步收集的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);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
log.info("蓝球分析算法完成,结果:{}", result);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 蓝球分析算法主方法(带筛选过程说明)
|
|
|
|
|
|
* @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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 验证蓝球分析算法的输入参数
|
|
|
|
|
|
*/
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 蓝球多级回退选择:先用 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 从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();
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
switch (level) {
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
result.add(t8List.get(i).getSlaveBallNumber());
|
|
|
|
|
|
}
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理第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);
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
break;
|
2025-08-08 19:18:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
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++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
result.add(t8List.get(i).getSlaveBallNumber());
|
|
|
|
|
|
}
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 上面两个中的第一个(系数更大的那个):索引 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
break;
|
2025-08-08 19:18:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
case "L": {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 低位:取系数倒数第二个向上5个;窗口顶部和底部位置均有毛边:使用blue_history_top_100/blue_history_top多级回退比较
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 先按基本规则选出5个位置的球号
|
|
|
|
|
|
List<Integer> baseResult = new ArrayList<>();
|
2025-08-08 19:18:50 +08:00
|
|
|
|
for (int i = startIndex; i <= endIndex; i++) {
|
2025-08-12 17:19:51 +08:00
|
|
|
|
baseResult.add(t8List.get(i).getSlaveBallNumber());
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
2025-08-08 19:18:50 +08:00
|
|
|
|
|
2025-08-12 17:19:51 +08:00
|
|
|
|
// 处理顶部位置(系数最大的)毛边:向上查找相同面系数
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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()));
|
2025-08-12 17:19:51 +08:00
|
|
|
|
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
|
2025-08-08 19:18:50 +08:00
|
|
|
|
);
|
2025-08-12 17:19:51 +08:00
|
|
|
|
if (!bottomSelected.isEmpty()) {
|
|
|
|
|
|
baseResult.set(baseResult.size() - 1, bottomSelected.get(0));
|
|
|
|
|
|
}
|
2025-08-08 19:18:50 +08:00
|
|
|
|
}
|
2025-08-12 17:19:51 +08:00
|
|
|
|
|
|
|
|
|
|
result = baseResult;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
break;
|
2025-08-08 19:18:50 +08:00
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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个,使用多级筛选机制
|
|
|
|
|
|
*/
|
2025-08-08 19:18:50 +08:00
|
|
|
|
private List<Integer> getSimpleTop4ByFrequency(List<Integer> allNumbers, List<BallWithCoefficient> step2BlueT8Coefficients) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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 {
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 会超过4个:第一级直接用“第二步收集的T8面系数求和”进行筛选
|
2025-08-01 19:09:57 +08:00
|
|
|
|
int remainingSlots = 4 - result.size();
|
2025-08-08 19:18:50 +08:00
|
|
|
|
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()));
|
2025-08-01 19:09:57 +08:00
|
|
|
|
result.addAll(selectedBalls);
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
log.info("通过第二步T8面系数和筛选完成,最终选择:{}", selectedBalls);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:18:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 统计频率并获取前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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 19:09:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 从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) {
|
2025-08-08 19:18:50 +08:00
|
|
|
|
log.debug("从T5表查询主球{}的线系数数据,取前12个(含毛边处理)", masterBallNumber);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 查询指定主球的所有数据,按线系数降序排列
|
|
|
|
|
|
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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 完全复刻补齐策略:
|
|
|
|
|
|
// 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++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 先加入所有大于阈值的球号
|
2025-08-01 19:09:57 +08:00
|
|
|
|
List<Integer> result = new ArrayList<>();
|
2025-08-08 19:18:50 +08:00
|
|
|
|
for (int i = 0; i < Math.min(strictlyGreaterCount, 12); i++) {
|
2025-08-01 19:09:57 +08:00
|
|
|
|
result.add(t5List.get(i).getSlaveBallNumber());
|
|
|
|
|
|
}
|
2025-08-08 19:18:50 +08:00
|
|
|
|
if (result.size() >= 12) {
|
|
|
|
|
|
log.debug("T5表主球{}最终选择的{}个从球:{}", masterBallNumber, result.size(), result);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 收集等于阈值的连续区间(紧随其后的相等区)
|
|
|
|
|
|
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);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
} else {
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 在等于组中先用 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);
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 19:18:50 +08:00
|
|
|
|
// 若仍不足12,从更小系数的条目继续顺序补足
|
|
|
|
|
|
idx = strictlyGreaterCount + equalsGroup.size();
|
|
|
|
|
|
while (result.size() < 12 && idx < t5List.size()) {
|
|
|
|
|
|
result.add(t5List.get(idx).getSlaveBallNumber());
|
|
|
|
|
|
idx++;
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.debug("T5表主球{}最终选择的{}个从球:{}", masterBallNumber, result.size(), result);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2025-08-04 18:16:17 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从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;
|
|
|
|
|
|
}
|
2025-08-01 19:09:57 +08:00
|
|
|
|
}
|