簡體   English   中英

如何在 optaplanner 中傳播學生的考試?

[英]How is it possible to spread exams of students in 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());
            }
        }
    }
}

有人可以幫我解決這個問題嗎?

提前致謝。

您提供的代碼顯示了對 Constraint Streams 如何工作的一個主要誤解,這反過來又從根本上破壞了它。

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

上面的代碼至少會讓這段代碼正常運行,但很可能會很慢。 有關如何更改域模型以提高約束性能的靈感,請參閱我最近的其他答案 它可能不完全適合,但這個想法似乎也適用於您的用例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM