如何在optaplanner中分散学生的考试?



我正在学习使用optaplanner。我需要分散一个学生的考试。一个学生两次考试的间隔时间越短,我给的罚分就越多。

我需要Student类的整数列表ExamIds,因为有那个学生的所有考试。

然后我需要检查所有这些考试计划的时间段,给他们更多的时间间隔。

我尝试的是以下代码:

Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Student.class)
.join(Exam.class)
.join(Exam.class)
.filter((student, exam1, exam2) ->{
if(student.getExamIds().contains(exam1.getID()) && student.getExamIds().contains(exam2.getID())){
if(exam1.getID() < exam2.getID()){
int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
if (timeDifference == 1) {
penalty = 16;
} else if (timeDifference == 2) {
penalty = 8;
} else if (timeDifference == 3) {
penalty = 4;
} else if (timeDifference == 4) {
penalty = 2;
} else if (timeDifference == 5) {
penalty = 1;
}
return true;
}
}
return false;
})
.penalize("Max time between exams", HardSoftScore.ofSoft(penalty));

我得到的结果是24645软处罚,但optaplanner甚至没有试图修复它们。我认为我在上面代码中检查考试的方式不完全正确。

这是约束类:

public class ExamTableConstraintProvider implements ConstraintProvider {
int penalty = 0;
List<Integer> studentExamIds;
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
// Hard constraints
twoExamForStudentConflict(constraintFactory),
// Soft constraints
spaceBetweenExams(constraintFactory)
};
}
private Constraint twoExamForStudentConflict(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Exam.class)
.join(Exam.class,
Joiners.equal(Exam::getTimeslot),
Joiners.lessThan(Exam::getID))
.filter((exam1, exam2) -> {
List<Integer> result = new ArrayList<>(exam1.getSID());
result.retainAll(exam2.getSID());
return result.size() > 0;
})
.penalize("Student conflict", HardSoftScore.ONE_HARD);
}
Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Student.class)
.join(Exam.class)
.join(Exam.class)
.filter((student, exam1, exam2) ->{
if(student.getExamIds().contains(exam1.getID()) && student.getExamIds().contains(exam2.getID())){
if(exam1.getID() < exam2.getID()){
int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
if (timeDifference == 1) {
penalty = 16;
} else if (timeDifference == 2) {
penalty = 8;
} else if (timeDifference == 3) {
penalty = 4;
} else if (timeDifference == 4) {
penalty = 2;
} else if (timeDifference == 5) {
penalty = 1;
}
return true;
}
}
return false;
})
.penalize("Max time between exams", HardSoftScore.ofSoft(penalty));
}
/*Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
penalty = 0;
return constraintFactory.forEach(Student.class)
.join(Exam.class,
equal(Student::getExamIds, Exam::getID),
filtering((student, exam1) -> exam1.getTimeslot() != null))
.join(Exam.class,
equal((student, exam1) -> student.getExamIds(), Exam::getID),
equal((student, exam1) -> exam1.getTimeslot(), Exam::getTimeslot),
filtering((student, exam1, exam2) -> {
int timeDifference = getPeriodBetweenExams(exam1, exam2);
if (timeDifference == 1) {
penalty += 16;
} else if (timeDifference == 2) {
penalty += 8;
} else if (timeDifference == 3) {
penalty += 4;
} else if (timeDifference == 4) {
penalty += 2;
} else if (timeDifference == 5) {
penalty += 1;
}
if(penalty == 0){
return false;
}
return true;
}))
.penalize("Max time between exams", HardSoftScore.ONE_SOFT);
}*/

}

这是我启动程序的类:

public class ExamTableApp {
private static final Logger LOGGER = LoggerFactory.getLogger(ExamTableApp.class);
public static void main(String[] args) {
SolverFactory<ExamTable> solverFactory = SolverFactory.create(new SolverConfig()
.withSolutionClass(ExamTable.class)
.withEntityClasses(Exam.class)
.withConstraintProviderClass(ExamTableConstraintProvider.class)
// The solver runs only for 5 seconds on this small dataset.
// It's recommended to run for at least 5 minutes ("5m") otherwise.
.withTerminationSpentLimit(Duration.ofSeconds(30)));
// Load the problem
ExamTable problem = getData();
// Solve the problem
Solver<ExamTable> solver = solverFactory.buildSolver();
ExamTable solution = solver.solve(problem);
// Visualize the solution
printTimetable(solution);
}
public static ExamTable getData(){
DataReader parser = new DataReader("benchmarks/sta-f-83.crs", "benchmarks/sta-f-83.stu");
List<Room> roomList = new ArrayList<>(1);
roomList.add(new Room(1,"Room A"));
//        roomList.add(new Room(2,"Room B"));
//        roomList.add(new Room(3,"Room C"));
List<Exam> examList = new ArrayList<>();
HashMap<Integer, Exam> exams = parser.getExams();
Set<Integer> keys = exams.keySet();
for (Integer i : keys) {
Exam exam = exams.get(i);
examList.add(new Exam(exam.getID(),  exam.getSID()));
}
List<Student> studentList = new ArrayList<>();
HashMap<Integer, Student> students = parser.getStudents();
Set<Integer> keys2 = students.keySet();
for (Integer i : keys2) {
Student student = students.get(i);
studentList.add(new Student(student.getID(), student.getExamIds()));
}
return new ExamTable(parser.getTimeslots(), roomList, examList, studentList);
}
private static void printTimetable(ExamTable examTable) {
LOGGER.info("");
List<Room> roomList = examTable.getRoomList();
List<Exam> examList = examTable.getExamList();
Map<TimeSlot, Map<Room, List<Exam>>> examMap = examList.stream()
.filter(exam -> exam.getTimeslot() != null && exam.getRoom() != null)
.collect(Collectors.groupingBy(Exam::getTimeslot, Collectors.groupingBy(Exam::getRoom)));
LOGGER.info("|            | " + roomList.stream()
.map(room -> String.format("%-10s", room.getName())).collect(Collectors.joining(" | ")) + " |");
LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
for (TimeSlot timeslot : examTable.getTimeslotList()) {
List<List<Exam>> cellList = roomList.stream()
.map(room -> {
Map<Room, List<Exam>> byRoomMap = examMap.get(timeslot);
if (byRoomMap == null) {
return Collections.<Exam>emptyList();
}
List<Exam> cellLessonList = byRoomMap.get(room);
if (cellLessonList == null) {
return Collections.<Exam>emptyList();
}
return cellLessonList;
})
.collect(Collectors.toList());
LOGGER.info("| " + String.format("%-10s",
timeslot.getID() + " " + " | "
+ cellList.stream().map(cellLessonList -> String.format("%-10s",
cellLessonList.stream().map(Exam::getName).collect(Collectors.joining(", "))))
.collect(Collectors.joining(" | "))
+ " |"));
LOGGER.info("|" + "------------|".repeat(roomList.size() + 1));
}
List<Exam> unassignedExams = examList.stream()
.filter(exam -> exam.getTimeslot() == null || exam.getRoom() == null)
.collect(Collectors.toList());
if (!unassignedExams.isEmpty()) {
LOGGER.info("");
LOGGER.info("Unassigned lessons");
for (Exam exam : unassignedExams) {
LOGGER.info("  " + exam.getName() + " - " + exam.getNumberOfStudents() + " - " + exam.getSID());
}
}
}
}

有人能帮我一下吗?提前感谢。

您提供的代码显示了对约束流如何工作的一个主要误解,这反过来又使它从根本上被破坏了。

ConstraintProvider的任何实例必须是无状态的。尽管从技术上讲,您可以在该类中拥有字段,但它没有任何用处,因为约束(在运行时)需要没有副作用。这样的话,你可能已经引入了分数损坏,甚至可能没有注意到。

此外,约束权重HardScore.ofSoft(...)仅在约束创建期间计算一次,而不是在求解器运行时计算,因此像您那样定义它将是毫无意义的。(它的值将是实例化时penalty的值,因此是0。)您需要使用的是匹配权重,它是在运行时计算的。只做了这样的修改,所讨论的约束看起来像这样:

Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Student.class)
.join(Exam.class)
.join(Exam.class)
.filter((student, exam1, exam2) -> {
if(student.getExamIds().contains(exam1.getID()) && student.getExamIds().contains(exam2.getID())){
if(exam1.getID() < exam2.getID()){
return true;
}
}
return false;
})
.penalize("Max time between exams", HardSoftScore.ONE_SOFT,
(student, exam1, exam2) ->{
int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
if (timeDifference == 1) {
return 16;
} else if (timeDifference == 2) {
return 8;
} else if (timeDifference == 3) {
return 4;
} else if (timeDifference == 4) {
return 2;
} else {
return 1;
}
}
});
}

上面的代码至少可以使这个代码正常运行,但是它可能会很慢。有关如何更改域模型以提高约束性能的灵感,请参阅我最近的另一个回答。它可能不完全适合,但这个想法似乎对您的用例也是有效的。

最新更新