突如其来的疫情改变了人们生活学习的方方面面,大规模网上授课便是其中之一。
在排课问题上,网课是有一些优势的:网课是没有教室数量的限制,而且教室容量还是无限的,使得一位老师同时给几万名学生上课,成为可能(思政类课程)。
在排课问题上,网课的劣势也是显而易见的:大规模的学生在同一时段上课引起的冲突变得更加不好处理,而网课同时要求在相对短的时间段内完成多门课的排课,这会增加课程的密度,加大解决排课问题的复杂度。由于网课没有了教学资源的限制,这又引出了一个新的需求,多门课程合并上课,比如某些课程在A专业的学生来说是专业课;可在对B专业的学生是专业基础课,而课程内容95%是相同的,类似这样不同的课程代码要合并在一起上课。在数据结构上,同一位老师,在同一时刻要在同一教室上多门课,这是传统排课思路所无法理解的。
前段时间老婆就遇到了这样的问题:多轮次、高频度,大密度,大规模的网课如何进行教学安排呢?问题涉及200门课,几万名学生选课,人均选课门数5.5门。
网课的排课思路与传统课程完全不同,在给定的限制时间段内,即便经验丰富的教务员也很难排出合适的网课课表。我想起自己曾经用遗传算法做过安排考场的项目,应用场景跟排课非常相似,便自告奋勇地说要帮她写个程序来实现程序自动排课。
需求大致清楚了,下面说说解题思路:
需要整理出2份excel数据喂给程序,如下图:原始数据脱敏后,会提供下载。
第一张表是课程id与教师id对应关系,用于确保一位教师,同一时间只能上一门课。
第二张表是用于确定课程id与班级id的关系,确保同一个自然班在同一时段只有一门课要上,自然班不冲突,该班的所有学生均不会冲突。
其中999开头的课程id是虚拟id,实际数据中是不存在的,用它们表示合并后的课程。
定义课表类:
class Schedule:
"""课表类
"""
def __init__(self, courseId, teacherId):
self.courseId = courseId
self.teacherId = teacherId
self.classId = []
self.liveTime = 0
def random_init(self, liveTimeSize):
"""随机初始化
参数:
liveTimeSize: int, 可排课时段总数.
"""
self.liveTime = np.random.randint(1, liveTimeSize + 1, 1)[0]
设计损失函数,计算冲突值并返回精英序号:
def schedule_cost(population, elite):
"""计算种群冲突
声明:
population: List, 种群类型为List,课表类
elite: int, 种群中选取的精英数
Returns:
index of best result.
best conflict score.
"""
conflicts = []
n = len(population[0])
for p in population:
conflict = 0
#从第1门课开始,依次对比其后所有的课程。
for i in range(0, n - 1):
for j in range(i + 1, n):
# 同一老师,同一时间,只能上一门课
if p[i].teacherId == p[j].teacherId and p[i].liveTime == p[j].liveTime:
conflict += 1
# 同一自然班,同一时间,只能上一门课
if p[i].liveTime == p[j].liveTime:
#同一时间的课程安排,再做判断是否有自然班冲突。
""" 循环历遍效率不高
for cia in (p[i].classId):
for cib in (p[j].classId):
if cia == cib:
conflict += 1
"""
# set()使两个list去重后取长,与两个list直接取长的和,做差即为冲突数。
conflict += len(p[i].classId)+len(p[j].classId)-len(list(set(p[i].classId+p[j].classId)))
conflicts.append(conflict)
index = np.array(conflicts).argsort()
return index[: elite], conflicts[index[0]]
设计交叉函数、变异函数等就不帖了,后面提供下载。
实际结果是大概花了两三天的时间,写好的程序,又花了一个星期的时间进行各种调试和参数调优,最终排出了用6个时段完成200门课,几万名学生的课表编制工作,要知道学生的教学计划中课程最多就是六门,六个时段已经是理论上能排出的无冲突课表的极值。调试的这几天,手上的工作站辛苦了,每天晚上十几个小时都处于下面这种状态:
最后把程序和实际数据贴出来,分享一下,这是第一个版本,后期准备引入动态天灾机制和基因改造机制,使得程序能更高效地完成排课任务。具体的构想已经有了初步考虑,有机会再发一篇博客再细细道来吧。
程序源码及初始数据