如何为Timefold排程示例添加教师休息日约束?
实现Timefold中“特定教师仅在特定日期工作”的约束
问题背景
我在扩展Timefold的Java“Hello World”示例时,添加了自定义的Lesson、Class和Teacher数据,想要实现“特定教师仅能在特定日期工作”的约束,但编写的约束代码报错。
错误信息:
The method forEachUniquePair(Class, BiJoiner<A,A>, BiJoiner<A,A>) in the type ConstraintFactory is not applicable for the arguments (Class
, BiJoiner<Lesson,Lesson>, BiJoiner<Teacher,Teacher>)
我的约束代码:
Constraint restDayConstraint(ConstraintFactory constraintFactory) { // teachers don't teach on their rest day return constraintFactory .forEachUniquePair(Lesson.class, Teacher.class, Joiners.equal(Lesson::getTimeslot), Joiners.equal(Teacher::getTeacher)) .filter((lesson1, teacher) -> lesson1.getTimeslot().getDayOfWeek() != teacher.getRestDay()) .penalize(HardSoftScore.ONE_HARD) .justifyWith( (lesson1, teacher, score) -> new TeacherConflictJustification(lesson1.getTeacher(), lesson1, teacher)) .asConstraint("Teacher conflict"); }
Teacher类代码:
package org.acme.schooltimetabling.domain; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @PlanningEntity public class Teacher { @PlanningId private Long id; private String subject; private String restDay; @PlanningVariable private Timeslot timeslot; @PlanningVariable private Room room; // No-arg constructor required for Timefold public Teacher() { } public Teacher(long id, String subject, String restDay) { this.id = id; this.subject = subject; this.restDay = restDay; } @Override public String toString() { return subject + "(" + id + ")"; } // ************************************************************************ // Getters and setters // ************************************************************************ public Long getId() { return id; } public String getRestDay() { return restDay; } public String getSubject() { return subject; } }
错误原因
forEachUniquePair方法使用错误:该方法仅用于同一类型的两个实体配对约束,你传入了Lesson.class和Teacher.class两种不同类型,导致方法签名不匹配。- Teacher类定位错误:在课程表场景中,Teacher是固定的问题事实(ProblemFact),不是需要被分配的规划实体(PlanningEntity),不应该添加
@PlanningEntity和@PlanningVariable注解。
修正方案
第一步:修正Teacher类定义
将Teacher改为问题事实,移除规划实体相关注解:
package org.acme.schooltimetabling.domain; import ai.timefold.solver.core.api.domain.lookup.PlanningId; // 移除@PlanningEntity注解 public class Teacher { @PlanningId private Long id; private String subject; private String restDay; // 移除@PlanningVariable注解的timeslot和room字段(这些属性属于Lesson规划实体) // No-arg constructor required for Timefold public Teacher() { } public Teacher(long id, String subject, String restDay) { this.id = id; this.subject = subject; this.restDay = restDay; } @Override public String toString() { return subject + "(" + id + ")"; } // Getters and setters public Long getId() { return id; } public String getRestDay() { return restDay; } public String getSubject() { return subject; } }
第二步:修正约束代码
通过遍历Lesson,关联其对应的Teacher,检查课程时间是否与教师休息日冲突:
Constraint teacherRestDayConstraint(ConstraintFactory constraintFactory) { return constraintFactory // 遍历所有Lesson规划实体 .forEach(Lesson.class) // 关联Lesson对应的Teacher(假设Lesson有getTeacher()方法返回关联的Teacher对象) .join(Teacher.class, Joiners.equal(Lesson::getTeacher)) // 过滤出违反约束的情况:课程时间段的星期等于教师休息日 .filter((lesson, teacher) -> lesson.getTimeslot().getDayOfWeek().equals(teacher.getRestDay())) // 对每个违反情况施加硬惩罚 .penalize(HardSoftScore.ONE_HARD) .justifyWith((lesson, teacher, score) -> new TeacherConflictJustification(teacher, lesson, "教师" + teacher.getSubject() + "在休息日" + teacher.getRestDay() + "被安排课程")) .asConstraint("Teacher cannot teach on rest day"); }
额外优化建议
将Teacher的restDay字段类型从String改为java.time.DayOfWeek枚举,避免字符串格式错误导致的匹配问题,代码更健壮:
// Teacher类中修改字段类型 private DayOfWeek restDay; // 构造方法调整 public Teacher(long id, String subject, DayOfWeek restDay) { this.id = id; this.subject = subject; this.restDay = restDay; } // 约束过滤条件调整 .filter((lesson, teacher) -> lesson.getTimeslot().getDayOfWeek() == teacher.getRestDay())
内容的提问来源于stack exchange,提问作者Peter C
相关产品推荐
相关产品推荐

