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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 02:50:21