如何在OptaPlanner中用EasyScoreCalculator记录扣分原因?
解决OptaPlanner中记录软分扣分说明的问题
核心问题分析
你之前的两种方案都存在OptaPlanner运行机制上的适配问题:
- 实体属性存储:OptaPlanner在评估过程中会频繁克隆解决方案和实体,你的清空操作可能没对应到当前评估的方案实例,导致说明信息混乱或丢失。
- 静态列表存储:OptaPlanner默认是多线程并行评估不同解决方案的,静态列表会被多个线程同时写入,必然混入其他非最优方案的信息。
正确实现方案
利用解决方案(Solution)类的自定义属性来存储当前方案的扣分说明,结合EasyScoreCalculator的无状态评估逻辑,确保每次评估只针对当前方案记录信息。
步骤1:修改Solution类
给你的Solution类添加扣分说明列表,并确保克隆时能正确复制该属性(OptaPlanner评估时会克隆方案):
public class TimetableSolution implements Solution<HardSoftScore> { // 原有属性(如lessonList、teacherList等)... private List<String> scoreExplanations = new ArrayList<>(); // Getter & Setter public List<String> getScoreExplanations() { return scoreExplanations; } public void setScoreExplanations(List<String> scoreExplanations) { this.scoreExplanations = scoreExplanations; } // 实现DeepCloneable接口,确保克隆时复制说明列表 @Override public TimetableSolution deepClone() { TimetableSolution clone = new TimetableSolution(); // 复制原有属性... clone.setLessonList(new ArrayList<>(this.getLessonList())); // 复制扣分说明列表(新ArrayList避免引用共享) clone.setScoreExplanations(new ArrayList<>(this.scoreExplanations)); return clone; } }
步骤2:修改EasyScoreCalculator
在每次评估开头清空当前方案的说明列表,触发扣分逻辑时直接写入当前方案的列表:
public class TimetableEasyScoreCalculator implements EasyScoreCalculator<TimetableSolution, HardSoftScore> { @Override public HardSoftScore calculateScore(TimetableSolution solution) { // 关键:每次评估前清空当前方案的说明,避免残留历史数据 solution.getScoreExplanations().clear(); int hardScore = 0; int softScore = 0; // 遍历实体执行自定义扣分逻辑 for (Lesson lesson : solution.getLessonList()) { // 示例:检查任务紧随逻辑 Lesson precedingLesson = findPrecedingLesson(solution, lesson); if (precedingLesson != null && "Y".equals(precedingLesson.getTaskName())) { int deduction = 5; softScore -= deduction; // 写入当前方案的说明列表 solution.getScoreExplanations().add( String.format("因任务%s紧随任务%s之后,扣除%d分", precedingLesson.getTaskName(), lesson.getTaskName(), deduction) ); } } return HardSoftScore.of(hardScore, softScore); } // 辅助方法:根据你的业务逻辑找到当前lesson的前置任务 private Lesson findPrecedingLesson(TimetableSolution solution, Lesson lesson) { // 实现你的排序/查找逻辑(比如按时间、教室等维度) return null; } }
步骤3:获取最优方案的扣分说明
当OptaPlanner求解完成后,直接从最优解决方案中提取说明:
Solver<TimetableSolution> solver = SolverFactory.createFromXmlResource("timetableSolverConfig.xml").buildSolver(); TimetableSolution bestSolution = solver.solve(initialSolution); // 输出所有扣分说明 for (String explanation : bestSolution.getScoreExplanations()) { System.out.println(explanation); }
进阶建议
如果后续性能要求较高,建议改用Constraint Streams API代替EasyScoreCalculator,它原生支持ConstraintMatch功能,无需手动维护说明列表,可直接通过ScoreManager获取每个约束的匹配详情和扣分原因。
内容的提问来源于stack exchange,提问作者MattWolc24
相关产品推荐
相关产品推荐

