java 如何在optaplanner中传播学生的考试?

fhg3lkii  于 2022-12-21  发布在  Java
关注(0)|答案(1)|浏览(81)

我正在学习使用optaplanner。我需要分散一个学生的考试。一个学生的两次考试之间的时间越少,我给的惩罚就越多。
我需要Student类的Integer List ExamId,因为存在该学生的所有考试。
然后,我需要检查所有这些考试计划的时间槽与对方给他们更多的时间之间。
我尝试的是以下代码:
'

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());
            }
        }
    }
}

有人能帮我一下吗?
先谢了。

brccelvz

brccelvz1#

您提供的代码显示了对约束流如何工作的一个主要误解,这反过来又使它从根本上被破坏。
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;
                        }
                }
            });
}

上面的代码至少可以让代码正常运行,但是速度可能会很慢。请参阅my other recent answer以获得如何更改域模型以提高约束性能的灵感。它可能不完全适合,但是这个想法似乎也适用于您的用例。

相关问题