13.5. TimeTableApp.java クラス
学校の時間割アプリケーションのすべてのコンポーネントを作成したら、それらをすべて TimeTableApp.java
クラスにまとめます。
main()
メソッドは以下のタスクを実行します。
-
SolverFactory
を作成して、各データセットのSolver
を構築します。 - データセットをロードします。
-
Solver.solve()
で解決します。 - そのデータセットの解を視覚化します。
通常、アプリケーションには、解決する問題データセットごとに新しい Solver
インスタンスを構築するための SolverFactory
が 1 つあります。SolverFactory
はスレッドセーフですが、Solver
はそうではありません。学校の時間割アプリケーションの場合、データセットは 1 つしかないため、Solver
インスタンスは 1 つだけです。
完成した TimeTableApp.java
クラスは次のとおりです。
package org.acme.schooltimetabling; import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.acme.schooltimetabling.domain.Lesson; import org.acme.schooltimetabling.domain.Room; import org.acme.schooltimetabling.domain.TimeTable; import org.acme.schooltimetabling.domain.Timeslot; import org.acme.schooltimetabling.solver.TimeTableConstraintProvider; import org.optaplanner.core.api.solver.Solver; import org.optaplanner.core.api.solver.SolverFactory; import org.optaplanner.core.config.solver.SolverConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TimeTableApp { private static final Logger LOGGER = LoggerFactory.getLogger(TimeTableApp.class); public static void main(String[] args) { SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig() .withSolutionClass(TimeTable.class) .withEntityClasses(Lesson.class) .withConstraintProviderClass(TimeTableConstraintProvider.class) // The solver runs only for 5 seconds on this small data set. // It's recommended to run for at least 5 minutes ("5m") otherwise. .withTerminationSpentLimit(Duration.ofSeconds(10))); // Load the problem TimeTable problem = generateDemoData(); // Solve the problem Solver<TimeTable> solver = solverFactory.buildSolver(); TimeTable solution = solver.solve(problem); // Visualize the solution printTimetable(solution); } public static TimeTable generateDemoData() { List<Timeslot> timeslotList = new ArrayList<>(10); timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30))); timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30))); timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30))); timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30))); timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30))); timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(8, 30), LocalTime.of(9, 30))); timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9, 30), LocalTime.of(10, 30))); timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(10, 30), LocalTime.of(11, 30))); timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(13, 30), LocalTime.of(14, 30))); timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(14, 30), LocalTime.of(15, 30))); List<Room> roomList = new ArrayList<>(3); roomList.add(new Room("Room A")); roomList.add(new Room("Room B")); roomList.add(new Room("Room C")); List<Lesson> lessonList = new ArrayList<>(); long id = 0; lessonList.add(new Lesson(id++, "Math", "A. Turing", "9th grade")); lessonList.add(new Lesson(id++, "Math", "A. Turing", "9th grade")); lessonList.add(new Lesson(id++, "Physics", "M. Curie", "9th grade")); lessonList.add(new Lesson(id++, "Chemistry", "M. Curie", "9th grade")); lessonList.add(new Lesson(id++, "Biology", "C. Darwin", "9th grade")); lessonList.add(new Lesson(id++, "History", "I. Jones", "9th grade")); lessonList.add(new Lesson(id++, "English", "I. Jones", "9th grade")); lessonList.add(new Lesson(id++, "English", "I. Jones", "9th grade")); lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "9th grade")); lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "9th grade")); lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade")); lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade")); lessonList.add(new Lesson(id++, "Math", "A. Turing", "10th grade")); lessonList.add(new Lesson(id++, "Physics", "M. Curie", "10th grade")); lessonList.add(new Lesson(id++, "Chemistry", "M. Curie", "10th grade")); lessonList.add(new Lesson(id++, "French", "M. Curie", "10th grade")); lessonList.add(new Lesson(id++, "Geography", "C. Darwin", "10th grade")); lessonList.add(new Lesson(id++, "History", "I. Jones", "10th grade")); lessonList.add(new Lesson(id++, "English", "P. Cruz", "10th grade")); lessonList.add(new Lesson(id++, "Spanish", "P. Cruz", "10th grade")); return new TimeTable(timeslotList, roomList, lessonList); } private static void printTimetable(TimeTable timeTable) { LOGGER.info(""); List<Room> roomList = timeTable.getRoomList(); List<Lesson> lessonList = timeTable.getLessonList(); Map<Timeslot, Map<Room, List<Lesson>>> lessonMap = lessonList.stream() .filter(lesson -> lesson.getTimeslot() != null && lesson.getRoom() != null) .collect(Collectors.groupingBy(Lesson::getTimeslot, Collectors.groupingBy(Lesson::getRoom))); LOGGER.info("| | " + roomList.stream() .map(room -> String.format("%-10s", room.getName())).collect(Collectors.joining(" | ")) + " |"); LOGGER.info("|" + "------------|".repeat(roomList.size() + 1)); for (Timeslot timeslot : timeTable.getTimeslotList()) { List<List<Lesson>> cellList = roomList.stream() .map(room -> { Map<Room, List<Lesson>> byRoomMap = lessonMap.get(timeslot); if (byRoomMap == null) { return Collections.<Lesson>emptyList(); } List<Lesson> cellLessonList = byRoomMap.get(room); if (cellLessonList == null) { return Collections.<Lesson>emptyList(); } return cellLessonList; }) .collect(Collectors.toList()); LOGGER.info("| " + String.format("%-10s", timeslot.getDayOfWeek().toString().substring(0, 3) + " " + timeslot.getStartTime()) + " | " + cellList.stream().map(cellLessonList -> String.format("%-10s", cellLessonList.stream().map(Lesson::getSubject).collect(Collectors.joining(", ")))) .collect(Collectors.joining(" | ")) + " |"); LOGGER.info("| | " + cellList.stream().map(cellLessonList -> String.format("%-10s", cellLessonList.stream().map(Lesson::getTeacher).collect(Collectors.joining(", ")))) .collect(Collectors.joining(" | ")) + " |"); LOGGER.info("| | " + cellList.stream().map(cellLessonList -> String.format("%-10s", cellLessonList.stream().map(Lesson::getStudentGroup).collect(Collectors.joining(", ")))) .collect(Collectors.joining(" | ")) + " |"); LOGGER.info("|" + "------------|".repeat(roomList.size() + 1)); } List<Lesson> unassignedLessons = lessonList.stream() .filter(lesson -> lesson.getTimeslot() == null || lesson.getRoom() == null) .collect(Collectors.toList()); if (!unassignedLessons.isEmpty()) { LOGGER.info(""); LOGGER.info("Unassigned lessons"); for (Lesson lesson : unassignedLessons) { LOGGER.info(" " + lesson.getSubject() + " - " + lesson.getTeacher() + " - " + lesson.getStudentGroup()); } } } }
main ()
メソッドは最初に SolverFactory
を作成します。
SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig() .withSolutionClass(TimeTable.class) .withEntityClasses(Lesson.class) .withConstraintProviderClass(TimeTableConstraintProvider.class) // The solver runs only for 5 seconds on this small data set. // It's recommended to run for at least 5 minutes ("5m") otherwise. .withTerminationSpentLimit(Duration.ofSeconds(5)));
SolverFactory
の作成により、以前に作成した @PlanningSolution
クラス、@PlanningEntity
クラス、および ConstraintProvider
クラスが登録されます。
終了設定または terminationEarly()
イベントがない場合、ソルバーは永久に実行されます。これを避けるために、ソルバーは解決時間を 5 秒に制限します。
5 秒後、main()
メソッドは問題をロードして解決し、解決策を出力します。
// Load the problem TimeTable problem = generateDemoData(); // Solve the problem Solver<TimeTable> solver = solverFactory.buildSolver(); TimeTable solution = solver.solve(problem); // Visualize the solution printTimetable(solution);
solve()
メソッドはすぐには戻りません。最適解を返す前に 5 秒間実行されます。
OptaPlanner は、利用可能な終了時間内に見つかった最適なソリューションを返します。NP 困難な問題の性質上、特に大規模なデータセットの場合、最適解が最適ではない可能性があります。終了時間を増やして、より良い解決策を見つけてください。
generateDemoData()
メソッドは、解決する学校の時間割の問題を生成します。
printTimetable()
メソッドは時刻表をコンソールにきれいに出力するので、スケジュールが適切かどうかを視覚的に簡単に判断できます。