输出:
在配置文件(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]]