日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

初学OptaPlanner-02- 基于Spring Boot实现一个简单课程表排班的实例

發(fā)布時間:2023/12/13 综合教程 26 生活家
生活随笔 收集整理的這篇文章主要介紹了 初学OptaPlanner-02- 基于Spring Boot实现一个简单课程表排班的实例 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。