Timefold获取多个最优解:基于recommendFit或SolverEventListener的排课示例
在Timefold中获取多个最优解(学生-教师课程表场景)
以下提供两种实现方式,分别通过事件监听和SolutionManager的推荐方法来获取多个最优/候选解:
方法一:通过SolverEventListener监听bestSolutionChanged收集解
该方法在求解过程中实时捕获新出现的最优解,可收集得分更优或同得分但结构不同的候选解。
简化领域模型
// 解决方案类 @PlanningSolution public class CourseSchedule { @PlanningScore private HardSoftScore score; @ProblemFactCollectionProperty private List<Student> students; @ProblemFactCollectionProperty private List<Teacher> teachers; @ProblemFactCollectionProperty private List<Course> courses; @PlanningEntityCollectionProperty private List<StudentCourseAssignment> assignments; // 构造器、getter、setter省略 } // 规划实体类 @PlanningEntity public class StudentCourseAssignment { @PlanningId private Long id; private Student student; private Course course; @PlanningVariable(valueRangeProviderRefs = "teacherRange") private Teacher teacher; @PlanningVariable(valueRangeProviderRefs = "timeSlotRange") private TimeSlot timeSlot; // 构造器、getter、setter省略 } // 基础实体类(Student/Teacher/Course/TimeSlot)仅需包含id、name等基础属性,此处省略
求解与监听器实现
public class CourseScheduleSolver { public static void main(String[] args) { SolverFactory<CourseSchedule> solverFactory = SolverFactory.createFromXmlResource("courseScheduleSolverConfig.xml"); Solver<CourseSchedule> solver = solverFactory.buildSolver(); // 存储多个最优解的集合(必须深拷贝,避免原实例被修改) List<CourseSchedule> bestSolutions = new ArrayList<>(); HardSoftScore currentBestScore = null; // 添加最优解变更监听器 solver.addEventListener(new SolverEventListener<>() { @Override public void bestSolutionChanged(BestSolutionChangedEvent<CourseSchedule> event) { CourseSchedule newBest = event.getNewBestSolution(); HardSoftScore newScore = newBest.getScore(); // 得分更优则更新并保存 if (currentBestScore == null || newScore.compareTo(currentBestScore) > 0) { currentBestScore = newScore; bestSolutions.add(deepCopySolution(newBest)); } // 得分相同且解结构不同则保存 else if (newScore.equals(currentBestScore)) { boolean isDuplicate = bestSolutions.stream() .anyMatch(s -> isSolutionDuplicate(s, newBest)); if (!isDuplicate) { bestSolutions.add(deepCopySolution(newBest)); } } } }); // 初始化问题数据 CourseSchedule problem = createCourseScheduleProblem(); // 启动求解 solver.solve(problem); // 输出结果 System.out.println("收集到的最优解数量:" + bestSolutions.size()); for (int i = 0; i < bestSolutions.size(); i++) { System.out.printf("第%d个解得分:%s%n", i+1, bestSolutions.get(i).getScore()); } } // 深拷贝解决方案(避免原实例被求解过程修改) private static CourseSchedule deepCopySolution(CourseSchedule original) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(original); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (CourseSchedule) ois.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("深拷贝失败", e); } } // 判断解是否重复(根据业务场景自定义) private static boolean isSolutionDuplicate(CourseSchedule s1, CourseSchedule s2) { if (s1.getAssignments().size() != s2.getAssignments().size()) return false; for (int i = 0; i < s1.getAssignments().size(); i++) { StudentCourseAssignment a1 = s1.getAssignments().get(i); StudentCourseAssignment a2 = s2.getAssignments().get(i); if (!a1.getTeacher().equals(a2.getTeacher()) || !a1.getTimeSlot().equals(a2.getTimeSlot())) { return false; } } return true; } // 初始化问题数据(示例) private static CourseSchedule createCourseScheduleProblem() { // 此处省略学生、教师、课程、待分配任务的初始化逻辑 return new CourseSchedule(); } }
关键注意事项
- 必须对解进行深拷贝:Timefold求解时会复用同一个解决方案实例,直接保存引用会导致后续修改覆盖已存储的解。
- 重复解判断逻辑需贴合业务:比如可通过比较所有课程分配的教师、时间槽是否一致来判定重复。
- 可自定义收集规则:比如只保留得分最高的前N个不同解,或收集所有得分高于阈值的解。
方法二:使用SolutionManager的recommendFit方法生成推荐解
该方法基于已求解得到的最优解,生成多个符合硬约束、软约束权衡不同的候选解,适合求解完成后获取替代方案。
实现代码
public class CourseScheduleSolutionManager { public static void main(String[] args) { SolverFactory<CourseSchedule> solverFactory = SolverFactory.createFromXmlResource("courseScheduleSolverConfig.xml"); SolutionManager<CourseSchedule, HardSoftScore> solutionManager = solverFactory.getSolutionManager(); // 先求解得到基础最优解 Solver<CourseSchedule> solver = solverFactory.buildSolver(); CourseSchedule problem = createCourseScheduleProblem(); CourseSchedule baseBestSolution = solver.solve(problem); // 配置推荐请求 RecommendFitRequest<CourseSchedule> request = RecommendFitRequest.create() .solution(baseBestSolution) .recommendationCount(5) // 指定生成5个推荐解 // 可选:调整约束权重,生成不同侧重的解(比如优先满足学生偏好) .constraintWeightOverrides(ConstraintWeightOverrides.create() .setConstraintWeight("StudentCourseAssignment", "StudentPreferenceScore", HardSoftScore.ofSoft(2)) ) .build(); // 生成推荐解 RecommendFitResult<CourseSchedule> result = solutionManager.recommendFit(request); // 输出结果 System.out.println("生成的推荐解数量:" + result.getRecommendedSolutions().size()); for (int i = 0; i < result.getRecommendedSolutions().size(); i++) { CourseSchedule recommended = result.getRecommendedSolutions().get(i); System.out.printf("第%d个推荐解得分:%s%n", i+1, recommended.getScore()); } } // 复用前面的createCourseScheduleProblem方法 private static CourseSchedule createCourseScheduleProblem() { return new CourseSchedule(); } }
关键注意事项
recommendFit生成的解会满足所有硬约束:确保没有课程冲突、教师时间冲突等问题。- 约束权重调整可生成差异化解:比如调整学生偏好约束的权重,生成更贴合学生需求的候选方案。
- 实际生成数量可能少于指定值:如果无法生成足够多的不同解,会返回现有可行的候选解。
内容的提问来源于stack exchange,提问作者MZ7
相关产品推荐
相关产品推荐

