13.5. TimeTableApp.java 类
在创建了 school timetable 应用的所有组件后,您将把它们放到 TimeTableApp.java 类中。
main() 方法执行以下任务:
-
创建
Solvy,为每个数据集构建 Solver。eronnectionFactory - 加载数据集。
-
通过 Solv
er.solve()解决它。 - 视觉化呈现该数据集的解决方案。
通常,一个应用程序有一个 SolverFactory,用于为每个问题数据设置一个新的 Solver 实例,用于解决每个问题数据。SolverFactory 是线程安全,但 Solver 不是。对于 school timetable 应用,只有一个数据集,因此只有一个 Solver 实例。
以下是完成的 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());
}
}
}
}
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<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);
// 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() 方法会生成 school timetable 问题来解决。
printTimetable() 方法给控制台而生动,因此易于视觉确定是否适合自己的时间表。