基于Spring Boot的Time-fold时间表应用实体注解方案咨询
Time-fold 时间表应用实体标注与约束实现方案
一、基础枚举定义(复用你已有的枚举)
public enum Domain { MATH, ENGLISH, SCIENCE, HISTORY } public enum RoomType { LECTURE_HALL, LAB, CLASSROOM }
二、核心实体标注
1. 问题事实实体(固定数据,无需规划)
这些实体是时间表的基础数据,用@ProblemFactProperty或@ProblemFactCollectionProperty标注,供约束计算使用。
教师实体(Teacher)
@Data @NoArgsConstructor @AllArgsConstructor public class Teacher { private Long id; private String name; private Domain domain; // 授课领域 // 教师可用时间段集合(如周一1-3节可用) @ProblemFactProperty private Set<TimeSlot> availableTimeSlots; }
教室实体(Room)
@Data @NoArgsConstructor @AllArgsConstructor public class Room { private Long id; private String name; private RoomType type; // 按星期几配置每日最大课时(如周一最多3节) @ProblemFactProperty private Map<DayOfWeek, Integer> dailyMaxLessonCount; }
时间段实体(TimeSlot)
@Data @NoArgsConstructor @AllArgsConstructor public class TimeSlot { private Long id; private DayOfWeek dayOfWeek; private int period; // 第1节、第2节等序号 }
2. 规划实体(需要分配的对象)
课程/课时是核心规划对象,用@PlanningEntity标注,其需要分配的属性(教师、教室、时间段)用@PlanningVariable标注。
@PlanningEntity @Data @NoArgsConstructor @AllArgsConstructor public class Lesson { private Long id; private String subject; private Domain requiredDomain; // 课程要求的授课领域 private RoomType requiredRoomType; // 课程要求的教室类型 // 规划变量:分配的教师 @PlanningVariable(valueRangeProviderRefs = "teacherRange") private Teacher teacher; // 规划变量:分配的教室 @PlanningVariable(valueRangeProviderRefs = "roomRange") private Room room; // 规划变量:分配的时间段 @PlanningVariable(valueRangeProviderRefs = "timeSlotRange") private TimeSlot timeSlot; }
3. 规划解决方案容器
用@PlanningSolution标注,整合所有问题事实和规划实体,同时定义分数计算结果。
@PlanningSolution @Data @NoArgsConstructor @AllArgsConstructor public class TimetableSolution { // 教师集合:作为规划变量的可选范围 @ProblemFactCollectionProperty @ValueRangeProvider(id = "teacherRange") private List<Teacher> teacherList; // 教室集合:作为规划变量的可选范围 @ProblemFactCollectionProperty @ValueRangeProvider(id = "roomRange") private List<Room> roomList; // 时间段集合:作为规划变量的可选范围 @ProblemFactCollectionProperty @ValueRangeProvider(id = "timeSlotRange") private List<TimeSlot> timeSlotList; // 需要安排的课程集合 @PlanningEntityCollectionProperty private List<Lesson> lessonList; // 规划计算出的分数(硬约束不满足会导致硬分扣减) @PlanningScore private HardSoftScore score; }
三、约束规则实现
通过ConstraintProvider定义所有业务约束,分为硬约束(必须满足,否则方案无效)和软约束(优先满足,影响方案优劣)。
public class TimetableConstraintProvider implements ConstraintProvider { @Override public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { return new Constraint[] { // 硬约束:教师授课领域必须匹配课程要求 teacherDomainMatch(constraintFactory), // 硬约束:教室类型必须匹配课程要求 roomTypeMatch(constraintFactory), // 硬约束:教师必须在可用时间段授课 teacherAvailable(constraintFactory), // 硬约束:教室每日课时不超过设定最大值(如X教室周一最多3节) roomDailyMaxLessons(constraintFactory), // 硬约束:同一教师同一时间段不能排多门课 teacherNoOverlap(constraintFactory), // 硬约束:同一教室同一时间段不能排多门课 roomNoOverlap(constraintFactory) }; } private Constraint teacherDomainMatch(ConstraintFactory cf) { return cf.from(Lesson.class) .filter(lesson -> !lesson.getTeacher().getDomain().equals(lesson.getRequiredDomain())) .penalize("Teacher domain mismatch", HardSoftScore.ONE_HARD); } private Constraint roomTypeMatch(ConstraintFactory cf) { return cf.from(Lesson.class) .filter(lesson -> !lesson.getRoom().getType().equals(lesson.getRequiredRoomType())) .penalize("Room type mismatch", HardSoftScore.ONE_HARD); } private Constraint teacherAvailable(ConstraintFactory cf) { return cf.from(Lesson.class) .filter(lesson -> !lesson.getTeacher().getAvailableTimeSlots().contains(lesson.getTimeSlot())) .penalize("Teacher not available", HardSoftScore.ONE_HARD); } private Constraint roomDailyMaxLessons(ConstraintFactory cf) { return cf.from(Lesson.class) .groupBy(Lesson::getRoom, Lesson::getTimeSlot, ConstraintCollectors.count()) .filter((room, timeSlot, count) -> count > room.getDailyMaxLessonCount().get(timeSlot.getDayOfWeek())) .penalize("Room daily lesson limit exceeded", HardSoftScore.ONE_HARD, (room, timeSlot, count) -> count - room.getDailyMaxLessonCount().get(timeSlot.getDayOfWeek())); } private Constraint teacherNoOverlap(ConstraintFactory cf) { return cf.fromUniquePair(Lesson.class, Joiners.equal(Lesson::getTeacher), Joiners.equal(Lesson::getTimeSlot)) .penalize("Teacher overlap", HardSoftScore.ONE_HARD); } private Constraint roomNoOverlap(ConstraintFactory cf) { return cf.fromUniquePair(Lesson.class, Joiners.equal(Lesson::getRoom), Joiners.equal(Lesson::getTimeSlot)) .penalize("Room overlap", HardSoftScore.ONE_HARD); } }
四、Spring Boot 配置与使用
1. 配置 Solver 实例
@Configuration public class TimefoldConfig { @Bean public SolverFactory<TimetableSolution> solverFactory() { return SolverFactory.create(new SolverConfig() .withSolutionClass(TimetableSolution.class) .withEntityClasses(Lesson.class) .withConstraintProviderClass(TimetableConstraintProvider.class) .withTerminationSpentLimit(Duration.ofSeconds(30))); // 求解超时时间 } @Bean public Solver<TimetableSolution> solver(SolverFactory<TimetableSolution> solverFactory) { return solverFactory.buildSolver(); } }
2. 业务层调用求解
@Service public class TimetableService { private final Solver<TimetableSolution> solver; public TimetableService(Solver<TimetableSolution> solver) { this.solver = solver; } public TimetableSolution solveTimetable(TimetableSolution unsolvedSolution) { return solver.solve(unsolvedSolution); } }
关键说明
- 问题事实(Teacher、Room、TimeSlot)是固定数据,仅作为约束计算的参考,无需标记为规划实体。
- 规划变量(Lesson的teacher/room/timeSlot)必须指定可选范围(对应Solution中的
@ValueRangeProvider)。 - 硬约束的优先级高于软约束,所有硬约束满足时,才会优化软约束(可自行添加如教师课时均衡等软约束)。
内容的提问来源于stack exchange,提问作者MZ7
相关产品推荐
相关产品推荐

