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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 10:04:53