You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 09:27:02