i have a school timetable solver, When it had less data, It can quickly obtain answers, However, once the data becomes slightly larger, it becomes difficult for it to provide answers. I have been running it all day, and it still has 13 hard. I’d like to know where my problem lies. Is there an issue with my code, or is it just slow?
I have the following scheduling requirements:
My school has classes from Monday to Saturday each week.
There are 5 class periods in the morning and 4 class periods in the afternoon.
A regular class takes up one period.
A double class takes up two consecutive periods, and a double class cannot be scheduled in the last period of the morning or the first period of the afternoon.
Each regular class can only appear once per day, no repeats.
Similarly, each double class can only appear once per day, and a double class and a regular class cannot be scheduled on the same day.
A “5+1” schedule means 5 regular classes and 1 double class.
I have three class: Timeslot、Lesson、TimetableProblem
Timetable.java
package cn.iocoder.yudao.module.school.timefold.domain;
import ai.timefold.solver.core.api.domain.lookup.PlanningId;
import java.time.DayOfWeek;
public class Timeslot {
@PlanningId
private String id;
/**
* week
*/
private DayOfWeek dayOfWeek;
/**
* which period of the day:1~9
*/
private Integer sort;
...
}
Lesson.java
/**
* 规划实体
*/
@PlanningEntity
public class Lesson {
@PlanningId
private String id;
private String subject;
private String teacher;
private String grade;
/**
* double class flag
*/
private boolean continuousFlag;
/**
* if double class, there value is same
*/
private String continuousUuid;
@JsonIdentityReference
@PlanningVariable
private Timeslot timeslot;
...
}
TimeTableProblem.java
@PlanningSolution
public class TimeTableProblem {
private String name;
@ProblemFactCollectionProperty
@ValueRangeProvider
private List<Timeslot> timeslots;
@PlanningEntityCollectionProperty
private List<Lesson> lessons;
@PlanningScore
private HardSoftScore score;
private SolverStatus solverStatus;
...
}
and my ConstraintProvider is like this
public class TimeTableConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
gradeConflict(constraintFactory),
teacherConflict(constraintFactory),
continuousConflict(constraintFactory),
ordinaryAndContinuousConflict(constraintFactory),
continuousDayConflict(constraintFactory),
ordinaryConflict(constraintFactory)
};
}
/**
* A class can have only one lesson at most at the same time.
*
* @param constraintFactory
* @return
*/
public Constraint gradeConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEachUniquePair(Lesson.class,
equal(Lesson::getTimeslot),
equal(Lesson::getGrade))
.penalize(HardSoftScore.ONE_HARD)
.justifyWith((lesson1, lesson2, score) -> new GradeConflictJustification(lesson1.getGrade(), lesson1, lesson2))
.asConstraint("grade constraint");
}
/**
* A teacher can only teach one class at a time.
*
* @param constraintFactory
* @return
*/
public Constraint teacherConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEachUniquePair(Lesson.class,
equal(Lesson::getTimeslot),
equal(Lesson::getTeacher))
.penalize(HardSoftScore.ONE_HARD)
.justifyWith((lesson1, lesson2, score) -> new ContinuousJustification(lesson1.getTeacher(), lesson1, lesson2, score))
.asConstraint("teacher conflict");
}
/**
* double classes must be consecutive class periods on the same day.
*
* @param constraintFactory
* @return
*/
public Constraint continuousConflict(ConstraintFactory constraintFactory) {
List<Integer> avoidSort = Arrays.asList(5, 6);
return constraintFactory
.forEachUniquePair(Lesson.class, Joiners.equal(Lesson::getContinuousUuid))
.filter((lesson1, lesson2) -> lesson1.isContinuousFlag() && lesson2.isContinuousFlag())
.filter((lesson1, lesson2) -> Math.abs(lesson1.getTimeslot().getSort() - lesson2.getTimeslot().getSort()) != 1 ||
lesson1.getTimeslot().getDayOfWeek().getValue() != lesson2.getTimeslot().getDayOfWeek().getValue() ||
CollUtil.isEmpty(CollectionUtil.subtract(avoidSort, Arrays.asList(lesson1.getTimeslot().getSort(), lesson2.getTimeslot().getSort())))
)
.penalize(HardSoftScore.ONE_HARD)
.justifyWith((lesson1, lesson2, score) -> new ContinuousJustification(lesson1.getTeacher(), lesson1, lesson2, score))
.asConstraint("continuous conflict");
}
/**
* The consecutive class periods for the same subject and grade level cannot be scheduled on the same day as regular class periods.
*
* @param constraintFactory
* @return
*/
public Constraint ordinaryAndContinuousConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEachUniquePair(Lesson.class,
Joiners.equal(Lesson::getSubject),
Joiners.equal(Lesson::getGrade))
.filter((lesson1, lesson2) ->
lesson1.isContinuousFlag() != lesson2.isContinuousFlag() && lesson1.getTimeslot().getDayOfWeek().getValue() == lesson2.getTimeslot().getDayOfWeek().getValue()
)
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("ordinary continuous conflict");
}
/**
* The same subject cannot be scheduled for multiple consecutive classes within a single day.
*
* @param constraintFactory
* @return
*/
public Constraint continuousDayConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEachUniquePair(Lesson.class, Joiners.equal(Lesson::getSubject),
Joiners.equal(Lesson::getGrade))
.filter((lesson1, lesson2) -> lesson1.isContinuousFlag() && lesson2.isContinuousFlag() && !lesson1.getContinuousUuid().equals(lesson2.getContinuousUuid()) && lesson1.getTimeslot().getDayOfWeek().getValue() == lesson2.getTimeslot().getDayOfWeek().getValue())
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("continuous day conflict");
}
/**
* The same class is not allowed to have multiple regular class hours of the same subject in one day.
*
* @param constraintFactory
* @return
*/
public Constraint ordinaryConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEach(Lesson.class)
.filter(lesson -> !lesson.isContinuousFlag())
.groupBy(Lesson::getGrade, Lesson::getDayOfWeek, Lesson::getSubject, count())
.filter((grade, week, subject, count) -> count > 1)
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("ordinary conflict");
}
}
akafra is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
1