初学OptaPlanner-02- 基于Spring Boot实现一个简单课程表排班的实例
Spring Boot Java quick start
學(xué)習(xí)鏈接:
https://docs.optaplanner.org/7.45.0.Final/optaplanner-docs/html_single/index.html
01. 排班目標(biāo)
作出一個簡單的課程表timetable,示例如下:
時間表的類圖
02. Opta的常用注解說明, 關(guān)鍵實體類說明
@PlanningEntity
use it, OptaPlanner knows that this class changes during solving because it contains one or more planning variables.
@PlanningEntity類下的@PlanningVariable
作用,標(biāo)明具體的排班變量,示例課程類
@Data
@NoArgsConstructor
@PlanningEntity // so OptaPlanner knows that this class changes during solving because it contains one or more planning variables.
public class Lesson {
/**
* 前4個屬于固定輸入
*/
private Long id;
private String subject;
private String teacher;
private String studentGroup;
/**
* 這兩個對應(yīng)排班變量 會一直變動
* Refs 引用
*/
@PlanningVariable(valueRangeProviderRefs = "timeslotRange")
private Timeslot timeslot;
@PlanningVariable(valueRangeProviderRefs = "roomRange")
private Room room;
public Lesson(Long id, String subject, String teacher, String studentGroup) {
this.id = id;
this.subject = subject;
this.teacher = teacher;
this.studentGroup = studentGroup;
}
@Override
public String toString() {
return subject + "(" + id + ")";
}
}
兩個輸入數(shù)據(jù)類:教師類+課時槽類, Room+Timeslot
@Data
public class Room {
private String name;
}
@Data
public class Timeslot {
private String dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
}
@PlanningSolution
use it, OptaPlanner knows that this class contains all of the input and output data.
@PlanningSolution下的@ValueRangeProvider
作為輸入數(shù)據(jù)注入到 @PlanningVariable(valueRangeProviderRefs = "xxx")的注解下
@PlanningSolution下的@ProblemFactCollectionProperty
標(biāo)明輸入數(shù)據(jù)樂行
@PlanningSolution下的@PlanningEntityCollectionProperty
標(biāo)明輸出數(shù)據(jù)類型
@PlanningSolution下的@PlanningScore
輸出評分質(zhì)量: for example, 0hard/-5soft (硬約束扣0分, 軟約束扣了5分)
課程表輸入輸出數(shù)據(jù) 實體
@Data
@PlanningSolution // so OptaPlanner knows that this class contains all of the input and output data.
public class TimeTable {
@ValueRangeProvider(id = "timeslotRange") // 對應(yīng) @PlanningVariable下的id
@ProblemFactCollectionProperty // 輸入 不變
private List<Timeslot> timeslotList; // A timeslotList field with all time slots
@ValueRangeProvider(id = "roomRange") // 對應(yīng) @PlanningVariable下的id
@ProblemFactCollectionProperty // 輸入 不變
private List<Room> roomList; // 存儲所有的Room枚舉情況
/**
* 輸入時:
* 課程信息 subject, teacher and studentGroup 需要填入;
* timeslot and room fields 為空, timeslot and room fields 正是需要計算的.
*
* 輸出時:
* 輸出結(jié)果存儲在在Lesson的timeslot and room fields
*/
@PlanningEntityCollectionProperty // 輸出 結(jié)果域 (在計算過程中會一直進行嘗試,直到嘗試到最優(yōu)解)
private List<Lesson> lessonList;
/**
* 輸出評分質(zhì)量: for example, 0hard/-5soft (硬約束扣0分, 軟約束扣了5分)
*/
@PlanningScore
private HardSoftScore score;
private TimeTable() {
}
public TimeTable(List<Timeslot> timeslotList, List<Room> roomList,
List<Lesson> lessonList) {
this.timeslotList = timeslotList;
this.roomList = roomList;
this.lessonList = lessonList;
}
}
03. 約束/打分實體類
普通For循環(huán)寫法:
/**
* @description 課程表 簡單扣分的計算器
* @Date 2020/10/28 18:29
*/
public class TimeTableEasyScoreCalculator implements EasyScoreCalculator<TimeTable, HardSoftScore> {
@Override
public HardSoftScore calculateScore(TimeTable timeTable) {
int hardScore = 0;
for (Lesson a : timeTable.getLessonList()) {
for (Lesson b : timeTable.getLessonList()) {
if (a == b) {
continue;
}
// 雙層,不重復(fù),遍歷
// 硬約束: 在相同的timeslot里
if (a.getId() < b.getId() && a.getTimeslot() != null && a.getTimeslot().equals(b.getTimeslot())) {
// 一間教室最多只能容納一堂課
if(a.getRoom().equals(b.getRoom())) {
hardScore--;
}
// 一個教師最多只能上一堂課
if(a.getTeacher().equals(b.getTeacher())) {
hardScore--;
}
// 一個班級的學(xué)生也只能上一節(jié)課
if(a.getStudentGroup().equals(b.getStudentGroup())) {
hardScore--;
}
}
}
}
int softScore = 0;
return HardSoftScore.of(hardScore, softScore);
}
}
類似Java8的Stream流的寫法, 官網(wǎng)寫著可以降低時間復(fù)雜度:
/**
* @description 課程表 約束 生產(chǎn)者
* The ConstraintProvider scales an order of magnitude better than the EasyScoreCalculator: O(n) instead of O(n2).
* @Date 2020/10/29 10:29
*/
public class TimeTableConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
// Hard constraints
roomConflict(constraintFactory),
teacherConflict(constraintFactory),
studentGroupConflict(constraintFactory),
// Soft constraints are only implemented in the "complete" implementation
};
}
private Constraint roomConflict(ConstraintFactory constraintFactory) {
// 實現(xiàn)的就是TimeTableEasyScoreCalculator的: 一間教室同時只能容納一節(jié)課
return constraintFactory.from(Lesson.class)
.join(Lesson.class,
Joiners.equal(Lesson::getTimeslot),
Joiners.equal(Lesson::getRoom),
Joiners.lessThan(Lesson::getId))
// 加權(quán)重
.penalize("Room conflict", HardSoftScore.ONE_HARD);
}
private Constraint studentGroupConflict(ConstraintFactory constraintFactory) {
// 一個學(xué)生可以在同一時間 只能教授同一門課
return constraintFactory
.from(Lesson.class)
.join(Lesson.class,
Joiners.equal(Lesson::getTimeslot),
Joiners.equal(Lesson::getStudentGroup),
Joiners.lessThan(Lesson::getId))
// penalize 懲罰
.penalize("Stu conflict", HardSoftScore.ONE_HARD);
}
private Constraint teacherConflict(ConstraintFactory constraintFactory) {
// 一個教室可以在同一時間 只能上一門課
return constraintFactory
.from(Lesson.class)
.join(Lesson.class,
Joiners.equal(Lesson::getTimeslot),
Joiners.equal(Lesson::getTeacher),
Joiners.lessThan(Lesson::getId))
.penalize("Teacher conflict", HardSoftScore.ONE_HARD);
}
}
04. 測試類 (模擬輸入數(shù)據(jù))
/**
* 記得保持在和啟動類的統(tǒng)一目錄下
*/
@SpringBootTest
class OptaplannerApplicationTests {
@Resource
private SolverManager<TimeTable, UUID> solverManager;
/**
* 01 課程表測試
*/
@Test
void timeTableTest() {
String data = "{"timeslotList":[{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"}],"roomList":[{"name":"Room A"},{"name":"Room B"}],"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade"},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade"},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade"},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade"}]}";
TimeTable problem = JSON.parseObject(data, TimeTable.class);
UUID problemId = UUID.randomUUID();
// Submit the problem to start solving
SolverJob<TimeTable, UUID> solverJob = solverManager.solve(problemId, problem);
TimeTable solution;
try {
// Wait until the solving ends
solution = solverJob.getFinalBestSolution();
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException("Solving failed.", e);
}
System.out.println(solution);
}
}
05. 測試輸出課程表
{
"timeslotList": [ # 輸入數(shù)據(jù): 兩個上課時間段
{
"dayOfWeek": "MONDAY",
"startTime": "08:30:00",
"endTime": "09:30:00"
},
{
"dayOfWeek": "MONDAY",
"startTime": "09:30:00",
"endTime": "10:30:00"
}
],
"roomList": [ # 輸入數(shù)據(jù): 兩間教室
{
"name": "Room A"
},
{
"name": "Room B"
}
],
"lessonList": [ # 輸出結(jié)果
{
"id": 1,
"subject": "Math",
"teacher": "A. Turing",
"studentGroup": "9th grade",
"timeslot": { # 排班結(jié)果
"dayOfWeek": "MONDAY",
"startTime": "08:30:00",
"endTime": "09:30:00"
},
"room": { # 排班結(jié)果
"name": "Room A"
}
},
{
"id": 2,
"subject": "Chemistry",
"teacher": "M. Curie",
"studentGroup": "9th grade",
"timeslot": { # 排班結(jié)果
"dayOfWeek": "MONDAY",
"startTime": "09:30:00",
"endTime": "10:30:00"
},
"room": { # 排班結(jié)果
"name": "Room A"
}
},
{
"id": 3,
"subject": "French",
"teacher": "M. Curie",
"studentGroup": "10th grade",
"timeslot": { # 排班結(jié)果
"dayOfWeek": "MONDAY",
"startTime": "08:30:00",
"endTime": "09:30:00"
},
"room": { # 排班結(jié)果
"name": "Room B"
}
},
{
"id": 4,
"subject": "History",
"teacher": "I. Jones",
"studentGroup": "10th grade",
"timeslot": { # 排班結(jié)果
"dayOfWeek": "MONDAY",
"startTime": "09:30:00",
"endTime": "10:30:00"
},
"room": { # 排班結(jié)果
"name": "Room B"
}
}
],
"score": "0hard/0soft"
}
06. maven依賴
https://docs.optaplanner.org/7.45.0.Final/optaplanner-docs/html_single/index.html
07. 最后
時間太趕了, 邊學(xué)邊用,原理還不清楚,目前算是會調(diào)用這個黑盒了!
英文文檔,看著有點艱難,不過還好吧.
總結(jié)
以上是生活随笔為你收集整理的初学OptaPlanner-02- 基于Spring Boot实现一个简单课程表排班的实例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mac brew install 软件
- 下一篇: Win32小游戏--贪吃蛇