排课算法小记

输出:
排课算法小记_第1张图片

在配置文件(config.txt)中配置:老师,课程,专业班级,课时的信息,运行test.py自动生成对应班级课程表

eg:
专业1,四门课,每门课每周2课时,共8门课
专业2,四门课,每门课每周2课时
专业3,四门课,有三门课每周2课时,1门课每周1课时,每周共7门课
约束条件:

  • 同一个教室在同一个时间只能有一门课。
  • 同一个班级在同一个时间只能有一门课 。
  • 同一个教师在同一个时间只能有一门课。
  • 同一个班级在同一天不能有相同的课。

举个例子,增加一个班级305,一门3课时的课,一门2课时的课,老师和课程正常是一个老师开一门
运行一下,新的班级的五门课就加进去了

算法用的是遗传算法,先随机初始化,然后根据约束条件构建代价函数,不断迭代排序调整降低冲突量,冲突数为0,停止调整。
设置了500次的最大迭代次数,如果课程比较多,500次后冲突不为0,可以重新运行或者调大最大迭代次数。

课程超过了容量,就排不出来了

构思:
1.抽离约束条件

2.根据我们现有的课表分析,每天有8个课时,早上1234,下午5678,最小化每天时间为8份,前七份排课

3.设置成每天五节课也行,上午两节,下午三个班级的课程可以看做是三个独立的课程排课,也可当做每天三节课来排课

#班级:{301:1班,302:2班,303:3班}
#课程:{2001:诊断病理学,...}
#老师:{10001:A老师,...}
#课次:平均每周上几次课
#######
#老师 课程 班级 课次
10001 2001 301 2
10002 2002 301 2
10003 2003 301 2
10004 2004 301 2
10001 2001 302 2
10002 2002 302 2
10003 2003 302 2
10005 2005 302 2
10006 2006 303 1
10007 2007 303 2
import prettytable

from schedule import Schedule
from genetic import GeneticOptimize

import pandas as pd
# import openpyxl
def vis(schedule,name):
    """visualization Class Schedule.

    Arguments:
        schedule: List, Class Schedule
    """
    col_labels = ['week/slot', '1', '2', '3', '4', '5']
    table_vals = [[i + 1, '', '', '', '', ''] for i in range(6)]

    table = prettytable.PrettyTable(col_labels, hrules=prettytable.ALL)

    for s in schedule:
        weekDay = s.weekDay
        slot = s.slot
        text = ' 课程: {} \n 班级: {} \n 教室: {} \n 老师: {}'.format(s.courseId, s.classId, s.roomId, s.teacherId)
        table_vals[weekDay - 1][slot] = text

    for row in table_vals:
        table.add_row(row)

    pd_data=pd.DataFrame(table_vals,columns=['星期/时间','周一','周二','周三','周四','周五'])
    pd_data.to_csv(name,index=False,encoding='GBK')


if __name__ == '__main__':
    schedules = []
    vis_res = {}
    f=open('config.txt',encoding='utf-8')
    # 老师 课程 班级 课次
    for line in f:
        if line[:1]!='#':
            vis_res[int(line.split(' ')[2])]=list()
            for i in range(0,int(line.split(' ')[-1])):
                schedules.append(Schedule(int(line.split(' ')[1]), int(line.split(' ')[2]), int(line.split(' ')[0])))

    # optimization
    ga = GeneticOptimize(popsize=50, elite=10, maxiter=500)
    res = ga.evolution(schedules, 3)

    # visualization
    all_res=[]
    for r in res:
        all_res.append(r)
        vis_res[r.classId].append(r)
    vis(all_res,'总课表.csv')
    for key in vis_res.keys():
        vis(vis_res[key],'班级{}课表.csv'.format(key))

import copy
import numpy as np

from schedule import schedule_cost


class GeneticOptimize:
    """Genetic Algorithm.
    """

    def __init__(self, popsize=30, mutprob=0.3, elite=5, maxiter=100):
        # size of population
        self.popsize = popsize
        # prob of mutation
        self.mutprob = mutprob
        # number of elite
        self.elite = elite
        # iter times
        self.maxiter = maxiter

    def init_population(self, schedules, roomRange):
        """Init population

        Arguments:
            schedules: List, population of class schedules.
            roomRange: int, number of classrooms.
        """
        self.population = []

        for i in range(self.popsize):
            entity = []

            for s in schedules:
                s.random_init(roomRange)
                entity.append(copy.deepcopy(s))

            self.population.append(entity)

    def mutate(self, eiltePopulation, roomRange):
        """Mutation Operation

        Arguments:
            eiltePopulation: List, population of elite schedules.
            roomRange: int, number of classrooms.

        Returns:
            ep: List, population after mutation.
        """
        e = np.random.randint(0, self.elite, 1)[0]
        pos = np.random.randint(0, 2, 1)[0]

        ep = copy.deepcopy(eiltePopulation[e])

        for p in ep:
            pos = np.random.randint(0, 3, 1)[0]
            operation = np.random.rand()

            if pos == 0:
                p.roomId = self.addSub(p.roomId, operation, roomRange)
            if pos == 1:
                p.weekDay = self.addSub(p.weekDay, operation, 5)
            if pos == 2:
                p.slot = self.addSub(p.slot, operation, 5)

        return ep

    def addSub(self, value, op, valueRange):
        if op > 0.5:
            if value < valueRange:
                value += 1
            else:
                value -= 1
        else:
            if value - 1 > 0:
                value -= 1
            else:
                value += 1

        return value

    def crossover(self, eiltePopulation):
        """Crossover Operation

        Arguments:
            eiltePopulation: List, population of elite schedules.

        Returns:
            ep: List, population after crossover.
        """
        e1 = np.random.randint(0, self.elite, 1)[0]
        e2 = np.random.randint(0, self.elite, 1)[0]

        pos = np.random.randint(0, 2, 1)[0]

        ep1 = copy.deepcopy(eiltePopulation[e1])
        ep2 = eiltePopulation[e2]

        for p1, p2 in zip(ep1, ep2):
            if pos == 0:
                p1.weekDay = p2.weekDay
                p1.slot = p2.slot
            if pos == 1:
                p1.roomId = p2.roomId

        return ep1

    def evolution(self, schedules, roomRange):
        """evolution

        Arguments:
            schedules: class schedules for optimization.
            elite: int, number of best result.

        Returns:
            index of best result.
            best conflict score.
        """
        # Main loop .
        bestScore = 0
        bestSchedule = None

        self.init_population(schedules, roomRange)

        for i in range(self.maxiter):
            eliteIndex, bestScore = schedule_cost(self.population, self.elite)

            print('Iter: {} | conflict: {}'.format(i + 1, bestScore))

            if bestScore == 0:
                bestSchedule = self.population[eliteIndex[0]]
                break

            # Start with the pure winners
            newPopulation = [self.population[index] for index in eliteIndex]

            # Add mutated and bred forms of the winners
            while len(newPopulation) < self.popsize:
                if np.random.rand() < self.mutprob:
                    # Mutation
                    newp = self.mutate(newPopulation, roomRange)
                else:
                    # Crossover
                    newp = self.crossover(newPopulation)

                newPopulation.append(newp)

            self.population = newPopulation

        return bestSchedule

import numpy as np

class Schedule:
    """Class Schedule.
    """
    def __init__(self, courseId, classId, teacherId):
        """Init
        Arguments:
            courseId: int, unique course id.
            classId: int, unique class id.
            teacherId: int, unique teacher id.
        """
        self.courseId = courseId
        self.classId = classId
        self.teacherId = teacherId

        self.roomId = 0
        self.weekDay = 0
        self.slot = 0

    def random_init(self, roomRange):
        """random init.

        Arguments:
            roomSize: int, number of classrooms.
        """
        self.roomId = np.random.randint(1, roomRange + 1, 1)[0]
        self.weekDay = np.random.randint(1, 6, 1)[0]
        # self.slot = np.random.randint(1, 8, 1)[0]
        self.slot = np.random.randint(1, 6, 1)[0]

def schedule_cost(population, elite):
    """calculate conflict of class schedules.

    Arguments:
        population: List, population of class schedules.
        elite: int, number of best result.

    Returns:
        index of best result.
        best conflict score.
    """
    conflicts = []
    n = len(population[0])

    for p in population:
        conflict = 0
        for i in range(0, n - 1):
            for j in range(i + 1, n):
                # check course in same time and same room
                if p[i].roomId == p[j].roomId and p[i].weekDay == p[j].weekDay and p[i].slot == p[j].slot:
                    conflict += 1
                # check course for one class in same time
                if p[i].classId == p[j].classId and p[i].weekDay == p[j].weekDay and p[i].slot == p[j].slot:
                    conflict += 1
                # check course for one teacher in same time
                if p[i].teacherId == p[j].teacherId and p[i].weekDay == p[j].weekDay and p[i].slot == p[j].slot:
                    conflict += 1
                # check same course for one class in same day
                if p[i].classId == p[j].classId and p[i].courseId == p[j].courseId and p[i].weekDay == p[j].weekDay:
                    conflict += 1

        conflicts.append(conflict)

    index = np.array(conflicts).argsort()

    return index[: elite], conflicts[index[0]]

你可能感兴趣的:(算法,python)