Timefold排课:如何为特定科目设置优先连排2节课?
排课约束修改方案:特定科目优先2节连堂
核心修改思路
- 给语文、数学等特定科目设置更高权重的软奖励,确保Timefold优先为其安排2节连堂课;
- 新增约束惩罚同教师单日3节及以上的连课,避免出现过度连堂的情况;
- 保留普通科目的连课奖励但降低权重,避免覆盖特定科目的优先级。
修改后的代码实现
首先定义特定科目常量(可放在约束类中作为静态常量):
private static final Set<String> PRIORITY_SUBJECTS = Set.of("语文", "数学");
1. 特定科目2节连堂奖励约束
Constraint prioritySubjectDoublePeriod(ConstraintFactory constraintFactory) { return constraintFactory .forEach(Lesson.class) .filter(lesson -> PRIORITY_SUBJECTS.contains(lesson.getSubject())) .join(Lesson.class, Joiners.equal(Lesson::getTeacher), Joiners.equal(lesson -> lesson.getTimeslot().getDayOfWeek()), // 匹配严格连续的两节课(后一节课开始时间 = 前一节课结束时间) Joiners.filtering((lesson1, lesson2) -> lesson2.getTimeslot().getStartTime().equals(lesson1.getTimeslot().getEndTime())), // 避免重复统计同一组连课(比如lesson1&lesson2和lesson2&lesson1) Joiners.greaterThan(Lesson::getId, Lesson::getId)) // 确保当天该教师该科目只有这2节连课,避免和第三节连课叠加 .filter((lesson1, lesson2) -> { long sameSubjectSameDayCount = constraintFactory.from(Lesson.class) .filter(l -> l.getTeacher().equals(lesson1.getTeacher()) && l.getTimeslot().getDayOfWeek().equals(lesson1.getTimeslot().getDayOfWeek()) && l.getSubject().equals(lesson1.getSubject())) .count().value(); return sameSubjectSameDayCount == 2; }) // 给予高于普通连课的奖励权重,确保优先级 .reward(HardSoftScore.ofSoft(2)) .justifyWith((lesson1, lesson2, score) -> new TeacherTimeEfficiencyJustification(lesson1.getTeacher(), lesson1, lesson2)) .asConstraint("Priority subject double period"); }
2. 普通科目连课奖励约束(调整权重)
Constraint regularTeacherTimeEfficiency(ConstraintFactory constraintFactory) { return constraintFactory .forEach(Lesson.class) .filter(lesson -> !PRIORITY_SUBJECTS.contains(lesson.getSubject())) .join(Lesson.class, Joiners.equal(Lesson::getTeacher), Joiners.equal(lesson -> lesson.getTimeslot().getDayOfWeek())) .filter((lesson1, lesson2) -> { Duration between = Duration.between(lesson1.getTimeslot().getEndTime(), lesson2.getTimeslot().getStartTime()); return !between.isNegative() && between.compareTo(Duration.ofMinutes(30)) <= 0; }) // 奖励权重低于特定科目,避免抢占优先级 .reward(HardSoftScore.ONE_SOFT) .justifyWith((lesson1, lesson2, score) -> new TeacherTimeEfficiencyJustification(lesson1.getTeacher(), lesson1, lesson2)) .asConstraint("Regular teacher time efficiency"); }
3. 单日3节及以上连课惩罚约束
Constraint penalizeLongConsecutiveLessons(ConstraintFactory constraintFactory) { return constraintFactory .forEach(Lesson.class) .join(Lesson.class, Joiners.equal(Lesson::getTeacher), Joiners.equal(lesson -> lesson.getTimeslot().getDayOfWeek()), Joiners.filtering((lesson1, lesson2) -> { Duration between = Duration.between(lesson1.getTimeslot().getEndTime(), lesson2.getTimeslot().getStartTime()); // 仅针对属于连课范围的课程对 if (!between.isNegative() && between.compareTo(Duration.ofMinutes(30)) <= 0) { // 统计该教师当天在这个时间范围内的连续课程数量 long consecutiveCount = constraintFactory.from(Lesson.class) .filter(l -> l.getTeacher().equals(lesson1.getTeacher()) && l.getTimeslot().getDayOfWeek().equals(lesson1.getTimeslot().getDayOfWeek()) && l.getTimeslot().getStartTime().isAfterOrEqual(lesson1.getTimeslot().getStartTime()) && l.getTimeslot().getEndTime().isBeforeOrEqual(lesson2.getTimeslot().getEndTime())) .count().value(); // 当连续课程≥3节时触发惩罚 return consecutiveCount >= 3; } return false; })) // 每次触发扣1分,抵消多节连课的奖励收益 .penalize(HardSoftScore.ONE_SOFT) .asConstraint("Penalize long consecutive lessons"); }
关键修改说明
- 优先级区分:特定科目连课奖励(2分)高于普通科目(1分),Timefold会优先满足高权重的约束;
- 精准控制连课数量:通过统计当天同一科目的课程数,确保特定科目仅在恰好2节连课时获得高奖励,避免出现3节及以上连课;
- 惩罚过度连课:新增的惩罚约束会抵消多节连课的奖励,让Timefold更倾向于拆分长连课为2节一组;
- 避免重复统计:通过
Joiners.greaterThan(Lesson::getId, Lesson::getId)确保同一组连课只被计算一次奖励。
内容的提问来源于stack exchange,提问作者Hà Anh
相关产品推荐
相关产品推荐

