如何将OptaPlanner中规划实体的ConstraintStreams与关联问题事实属性结合?
解决课表中兼职教师课程紧凑性的约束方案
一、核心思路:通过分组聚合计算教师上课分布
你无需调整现有核心建模(Lesson作为规划实体、Teacher作为问题事实的设计是合理的),可以利用ConstraintStreams的groupBy功能,将同一教师的所有Lesson聚合起来,计算其上课天数、时段分布等全局属性,进而定义紧凑性约束。
二、具体实现步骤
1. 定义兼职教师判断逻辑
先实现判断教师是否属于需要紧凑安排的兼职人员(每周工作时长不足N小时):
// 示例:判断教师是否为需紧凑安排的兼职教师 private boolean isPartTimeTeacher(Teacher teacher, List<Lesson> lessons) { int totalWorkingHours = lessons.stream() .mapToInt(Lesson::getDurationHours) .sum(); return totalWorkingHours < N; // N为业务设定的时长阈值 }
2. 用ConstraintStreams实现紧凑性约束
通过分组聚合同一教师的所有课程,计算实际上课天数与理想天数的差值,对分散情况进行惩罚:
Constraint teacherScheduleCompactness(ConstraintFactory constraintFactory) { return constraintFactory.from(Lesson.class) // 按教师分组,聚合该教师的全部课程 .groupBy(Lesson::getTeacher, Collectors.toList()) // 过滤出需要紧凑安排的兼职教师 .filter((teacher, lessons) -> isPartTimeTeacher(teacher, lessons)) // 计算实际上课天数与理想天数的差值 .map((teacher, lessons) -> { Set<LocalDate> teachingDays = lessons.stream() .map(lesson -> lesson.getTimeslot().getDate()) .collect(Collectors.toSet()); int actualDays = teachingDays.size(); // 根据时长阈值N确定理想天数:比如N<8小时理想1天,8≤N<16理想2天 int idealDays = lessons.stream().mapToInt(Lesson::getDurationHours).sum() < 8 ? 1 : 2; // 实际天数超出理想天数的部分,作为惩罚依据 return Math.max(0, actualDays - idealDays); }) // 对每多出来的分散天数进行软惩罚(权重可根据业务优先级调整) .penalize("兼职教师课程分散", HardSoftScore.soft(100), excessDays -> excessDays); }
3. 与常规约束结合
将该紧凑性约束和你现有的常规约束(如同一时间教师不能上两门课)一同添加到ConstraintProvider的约束定义方法中即可:
@Override public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { return new Constraint[] { // 常规约束:同一时间教师不可同时授课 teacherConflict(constraintFactory), // 新增的紧凑性约束 teacherScheduleCompactness(constraintFactory), // 其他业务约束... }; }
三、优化细节
- 惩罚权重调整:如果紧凑性是优先级较高的软约束,可以调高
soft(100)中的数值,提升其在得分计算中的权重。 - 时段紧凑性进阶:若需要当天课程也尽量连排(避免中间空窗),可在分组后进一步计算单日时段的间隔,对空窗较多的情况额外增加惩罚。
- 性能优化:若课表数据量较大,可在Lesson实体中预先缓存所属日期,避免每次流计算重复解析时段日期。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

