首页 > 编程语言 >【排课小工具】排课程序设计与实现

【排课小工具】排课程序设计与实现

时间:2024-05-03 15:24:12浏览次数:24  
标签:排课 class 课表 teacher 课程 程序设计 工具 id day

课表的完整性意味着,可分配的节点的数量大小等于课表周数的累加和大小,为了进行完整性检测我需要两个对象:课表模板以及课程对象,从课表模板中获取可分配的节点数,从课程对象中获取该课程的上课周次。用户要求每个班级的课表模板相同,这使得完整性检测容易很多。

分级填充需求主要和课程的优先级有关,用户特别强调了要把数学、语文和科学尽可能的填充到每天的前两节课,从整体课表来看,数学分布在每天的第一节课或者第二节课,每周一共四节数学课一节科学课,这五节课应该均匀地分配到工作日的每一天中。语文每周有七节课,其中的五节课应该分配到每天的第一节课或者第二节课中,填补数学课和科学课的空缺。在优先级设计上,应该先排数学,然后排科学,接着排语文,剩下的课程放在后面排。

无时间冲突需求是排课项目的核心需求。前面提到的软件原型是这样满足这个需求的:首先创建一个全局课表,该课表是一个嵌套列表,一级子元素是每个班级的课表,二级子元素是指定班级星期,三级子元素是所属星期下的节次,接着通过课程信息表以及教师责任信息表计算出一个新表,该表包括三个重要的属性,分别是班级、课程名、教师名以及课程的周次,接下来开始了四层循环。第一层循环遍历(教师名,课程名)元组集合,并且根据每个元组获取该课程的周次以及该教师教授的班级。第二层循环遍历每个星期,第三层循环遍历每一天的每节课,第四层循环遍历每个班级。

为了满足需求一(无时间冲突),当某个节次被某个班级占据的时候就立刻跳出班级循环,转到下一个节次,即,在同一节次下不能有多个班级并行,而是只能存在一个班级。为了实现需求二(分级填充),第一层遍历的顺序由课程的优先级决定,尽管如此,仍然会出现个别次要科目排在每天的第二节课的情况。为了满足需求四(非连续性分配),我先根据周次计算出每天的最大排课次数,然后在排课的过程中将每天同一课程的排课次数限制在该数字的范围内。在该软件原型中,需求一和需求四得到了满足,需求二以及需求四得到部分满足,需求三并没有被兼顾到,尽管从结果上看系统没有漏掉任何一节课。

部分主程序代码
def process(template, course, duties, save_dir, show_teacher):
    schedules: list[list[list[str]]] = get_init_schedules(template)
    teacher_course_items = get_ordered_teacher_course_items(duties, course)
    q: Queue[int] = Queue()
    for d in [1, 5, 2, 4, 3]:
        q.put(d)
    for teacher_course_item in teacher_course_items:
        teacher_name, course_name = teacher_course_item[0]
        class_course_items = teacher_course_item[1]
        total_rest_lessons: dict[int, int] = {}
        day_rest_lessons: dict[int, int] = {}
        for class_course_item in class_course_items:
            class_id, total_lessons = class_course_item
            total_rest_lessons[class_id] = total_lessons
            day_rest_lessons[class_id] = int(total_lessons / 5) + 1
        for _ in range(5):
            day = q.get()
            q.put(day)
            day_rest_lessons_temp = deepcopy(day_rest_lessons)
            for lesson in range(1, 8):
                for class_id in day_rest_lessons_temp.keys():
                    unit_item_name = course_name if not show_teacher else course_name + f"({teacher_name})"
                    condition_1: bool = day_rest_lessons_temp[class_id] > 0
                    condition_2: bool = total_rest_lessons[class_id] > 0
                    if not (condition_1 and condition_2):
                        continue
                    condition_3: bool = (
                        schedules[class_id - 1][lesson - 1][day - 1] == ""
                    )
                    condition_4: bool = (
                        schedules[class_id - 1][lesson - 2][day - 1] != unit_item_name
                        if lesson > 1
                        else True
                    )
                    condition_5: bool = (
                        schedules[class_id - 1][lesson][day - 1] != unit_item_name
                        if lesson < 7
                        else True
                    )
                    if condition_3 and condition_4 and condition_5:
                        schedules[class_id - 1][lesson - 1][day - 1] = unit_item_name
                        day_rest_lessons_temp[class_id] -= 1
                        total_rest_lessons[class_id] -= 1
                        break
                if sum(day_rest_lessons_temp.values()) == 0:
                    break
            if sum(total_rest_lessons.values()) == 0:
                break
    filename = Path(save_dir).joinpath(f"年级课表.xls")
    save_excel(schedules, str(filename))

需求一(无时间冲突)与需求四(一天中非连续性分配)以及需求五(一周之内均匀分布)息息相关,它们都是取决于排课的算法是什么,因为在我的设定中,当一个课程一旦被安排到某个节点上,那么该节点就无法修改了,没有后期调整或者优化,所有的一切都是一次性完成。

在最新的设计中,只是对上述主程序代码做了稍许变动,最主要的变动是指针在课表上的运动路径。

最初的设计是这样的:首先遍历根据优先级排好序的(教师-课程-班级集合-课程周次-优先级)集合,这样就获取到了相关的数据,这一顺序就是在满足功能需求中的需求二(分级填充),优先级高的课程先分配。接下来第一个指针从星期一出发,依某种次序指向工作日的每一天。第二个指针从每天的上课的第一节课出发,依次指向一天中的每一节课,在该指针指向的每一个课次下,又一个指针指向课程集合的每一个班级,对于每一个班级,如果能够在该班级下实现课程的分配则立刻移动上一个指针(即指向节次的指针),这样就避免了在同一天的同一节课上一个教师出现在两个或两个以上的班级中,也就满足了需求一(无时间冲突)。为了实现需求四(同一天内非连续分配),在每个班级的指针下面添加了一个判断条件,该条件约束了如果上一个课程与当前课程相同,则放弃该节点,移动班级指针。综上,可以看到指针的顺序是:先移动星期,在移动节次,最后移动班级指针。在此模式下无法很好的满足需求五(一周内均匀分配:同一学科的课程应尽可能均摊到每一天中)。另外,也没有考虑需求三(完整整性检测)。

处理程序
def process(self):
        template_schedule = self.get_template_data()
        weekly_class_days = len(template_schedule[0])
        number_of_classes_per_day = len(template_schedule)
        ordered_items = self.get_ordered_courses_items()
        for ordered_item in ordered_items:
            course, teacher, classes, number_of_classes_per_week, priority = (
                ordered_item
            )
            number_of_remaining_courses = {
                class_id: number_of_classes_per_week for class_id in classes
            }
            if not self.teacher_schedule.get(teacher):
                self.teacher_schedule[teacher] = deepcopy(
                    self.get_teacher_schedule_tempalte()
                )
            for class_id in classes:
                if not number_of_remaining_courses[class_id] > 0:
                    continue
                if not self.class_schedule.get(class_id):
                    self.class_schedule[class_id] = deepcopy(self.get_template_data())
                for section in range(number_of_classes_per_day):
                    if not number_of_remaining_courses[class_id] > 0:
                        break
                    for day in range(weekly_class_days):
                        if not number_of_remaining_courses[class_id] > 0:
                            break
                        condition_1 = (
                            self.class_schedule[class_id][section][day] is None
                        )
                        condition_2 = (
                            (self.class_schedule[class_id][section - 1][day] != course)
                            if section > 0
                            else True
                        )
                        condition_3 = self.teacher_schedule[teacher][section][day] == ""
                        condition_4 = (
                            (
                                self.teacher_schedule[teacher][section - 1][day][0]
                                != class_id
                            )
                            if (section > 0)
                            and (priority < CONTINUOUS_BOUNDARY)
                            and isinstance(
                                self.teacher_schedule[teacher][section - 1][day], tuple
                            )
                            else True
                        )
                        if condition_1 and condition_2 and condition_3 and condition_4:
                            self.class_schedule[class_id][section][day] = course
                            self.teacher_schedule[teacher][section][day] = (
                                class_id,
                                course,
                            )
                            number_of_remaining_courses[class_id] -= 1
                        if (section == number_of_classes_per_day - 1) and (
                            day == weekly_class_days - 1
                        ):
                            self.missing_items.append(
                                (
                                    course,
                                    teacher,
                                    class_id,
                                    number_of_remaining_courses[class_id],
                                )
                            )

需求三(完整新检测)很容易满足,只需要计算出课表模板中有多少可以分配的节点,然后再计算出课程信息表中需要分配的课程节次总数,最后比对一下这两个数值是否相等就可以了,如果相等则说明课程信息数据是完整的,不多也不少。在新的设计中需要解决需求五(一周内均匀分配),为了满足这个需求,我改变了指针的运动方向:

这种跨星期的横向移动使得需求五(一周内均匀分布)很容易满足,为了兼顾满足需求四(一天之内非连续分布),我在最后一个指针下面添加了相似的判断条件。

在上一种指针移动方式下很容易规避时间冲突,而在新的设计中则不然,为了解决时间冲突问题,我创建了一个全局变量,该变量用来存放每个教师的时间安排表,这个表和课表一样也是两个维度,分别是星期和节次,只不过和正常课表不同的是,该课表是从教师的视角出发,相应节点元素为班级以及对应的课程。避免时间冲突的方式就是在每次分配的时候都要检查一下该教师视角下的课程表相应节点是否被占据。

到目前为止所有的需求在理论上都得到了满足,但在测试过程中还是出现了意料之外的情况。最终打印出来的课程表出现了空缺,可以确定的是这种空缺一定不是课程数缺失导致的,因为用户输入的课表模板以及课程信息表通过了完整性检测,经过测试发现有两种情况可能导致课表空缺:

第一,因为某课程违反了需求四(一天之内非连续分配)的要求,并且没有任何其他可用的空缺位置可用,以至于该课程在遍历课表的过程中,就算遍历到课表的最后一个节点都没有找到可用的节点。

第二,和第一种情况类似,但是更加棘手,那就是违反了需求一(无时间冲突)的需求。尽管从一开始的设计就试图避免时间冲突,但是仍然可能会发生这种状况。

产生第一种状况的原因是一种比需求四更强的需求,也就是,既不能让同一个班级在同一天中连续上同一个学科的课程,也不能让同一个教师在同一个班级在同一天中连续上课。为什么要添加这一要求呢?因为用户希望数学和科学尽可能不要在同一天上课,因为在该学校里,科学课一般都是数学老师代课,而一周中的科学课和数学课总次数刚好是 5 节课。因为这一额外要求,我增强了需求四,而现在为了解决第一种情况带来的问题,我需要在这一增强条件上做出妥协:当课程的优先级大小小于某个值的时候需要遵循增强要求,反之不需要遵循该要求。这样只需要适当的调整优先级就可以解决这个问题了。如果出现了该状况,我应该提醒用户将出现差错的科目的优先级值大小(值越低,优先级越高,越早被分配)调低。

对于第二种状况。我并没有在代码上有什么调整,和第一种情况一样解决的方案也是调整优先级,应该尽可能地将绑定在同一个教师下的课程设置成相同的优先级。

当前的程序可以允许以下变更:

第一,可以变更课程模板。调整那些固定学科或者特殊课程(类似于班会)的位置,用户在最开始的时候提到每个班级公用一个模板,所以这种变更会影响到所有班级。

第二,可以变更课程每周上课的次数。每次变更都需要确保变更后的上课次数的总数等于模板中可用于分配的节点数目。

第三,可以变更课程名。变更课程名后需要注意将职责信息表中课程哪一类同步更新。

第四,可以变更课程的优先级。更改优先级会使得课表上某些课程的次序向前或者向后排布,需要注意的是,变更优先级可能会造成漏排现象,最后遵循前面提到的规则。

第五可以变更教师的职责,只需要更改教师职责信息表的内容即可,这一更改相对比较自由。

第六,可以更改每天的上课次数,和更改课表模板同理。

用户在界面需求中提到希望能将输出结果设置为 Word 表格的样式,我分析了一下,一种解决思路就是将样板保存为 xml 格式,然后找到 xml 文件中需要填充课程名的位置,将其设置为占位符,然后在使用某些模板渲染引擎比如 jinjia 将课表的值渲染上去,最后在保存为 Word 文件。

先来看一看保存为 Excel 表格的输出模式。在前面提到,在程序中创建了两个全局变量,一个是常规的课程表,另一个为教师视角下的课程表,在最终的产品中,我可以添加一个是否输出教师课表的选项,丰富产品的功能,这一功能可以称之为附加功能。

在用户界面上并没有做太多更改,同样也是一个输入区域一个输出区域,我移除了是否显示教师视角下的课表这一选项,同时输出一般模式下的课表以及教师视角下的课表。

接下来可以开始着手编写用户使用文档了。

标签:排课,class,课表,teacher,课程,程序设计,工具,id,day
From: https://www.cnblogs.com/gaotianchi/p/18169341/course-scheduling-program-design-and-impleme

相关文章

  • Unity 热更--AssetBundle学习笔记 1.0【AB包资源加载工具类的实现】
    工具类封装通过上文中对AB包加载API的了解和简单使用,对AB包资源加载的几种方法进行封装,将其写入单例类中,如代码展示。确保每个AB资源包只加载一次:在LoadAssetBundleManager单例工具类中,首先提供基本的AB包及其AB包依赖包的加载方法,为保持AssetBundle只加载一次,使用DIctionary......
  • 下载工具使用总结
    IDM(InternetDownloadManager)DM是一个下载管理工具。它几乎可以下载任意内容,视频音频下载,网盘下载,还支持各种浏览器的插件。它还有自动化功能,可以定时下载,自动安排下载队列,还能批量下载功能。而且它还是多线程下载内容,让你的下载速度更快。PS:IDM是商业软件。只支持Win版......
  • C语言程序设计——字符串典型题练习
    1、计算一个字符串中最大的重复子串的字符的数量/********************************************************************* name : CalSubStrMaxCnt* function:计算一个字符串中最大的重复子串的字符的数量* argument:* @str:需要查找的字符串的地址* * ret......
  • 【排课小工具】面向对象分析探索领域模型
    用户向系统中输入课表模板、课程信息以及教师责任信息,系统以某种格式输出每个班级的课表。该用例中的主要参与者包括用户以及系统,除了上述两个主要参与者外,我们从该用例中抽取出可能有价值的名词:课表模板、课程、教师职责、班级以及课表。现在我们只知道下面图示的关系:在上一篇......
  • 推荐3款程序员常用的画图工具
    前言经常看到有小伙伴在DotNetGuide技术社区微信交流群里问:有什么好用的画图工具推荐的?今天大姚给大家推荐3款程序员日常工作中常用的画图工具,大家可以根据自己的需求选择。ProcessOnProcessOn是一款专业强大在线作图工具,提供AI生成思维导图流程图,支持思维导图、流程图、组织结......
  • 一个用Python将视频变为表情包的工具
    这是一个将视频转变为表情包的工具,现实生活中当我们看到一段搞笑的视频,我们可以将这段视频喂给这段程序,生成gif表情包,这样就可以用来舍友斗图了1、一些限制1、这个程序不能转化超过15秒以上的视频,因为占用的内存较高,会被终端杀死(除非你的计算机性能很好,也许1分钟的短视频都可以),......
  • 【MMD x EEVEE教程】工具篇 • blender设置
    这篇教程适合有一定基础的萌新....*&blender下载官方网址https://www.blender.org/官方blender,都是最新版,如果需要找旧版的blender可以到这里来https://download.blender.org/release/,里边包好了所有版本的blender,因为是做MMD,下载自己需要版本后,建议额外下载一个2.93版的,某些时......
  • 运用ETL工具,实现慧穗云数据管理
    在数字化时代,数据已成为企业发展的核心驱动力。然而,许多企业在面对庞大的数据量和多样的数据源时,往往面临着数据整合和转换的难题。为了解决这一问题,慧穗云与ETL工具(Extract, Transform, Load),为企业提供全方位的数据管理解决方案。 首先,让我们来了解一下慧穗云,慧穗云致力于为......
  • 测试的F12开发者工具
    1F12妙用复制文字某些不允许复制的网站,可以通过F12选中元素,快速复制编辑页面上的任何文本在控制台输入document.body.contentEditable="true"或者document.designMode="on"即可查看隐藏的密码选中密码所在的元素,将文本框类型从type="passwor......
  • CentOS7安装NVIDIA GPU驱动程序和CUDA工具包
      1.查看本地环境检查GPU型号lspci|grep-invidia查看linux系统版本uname-m&&cat/etc/redhat-release禁用nouveaulsmod|grepnouveau#打开如下文件sudovim/usr/lib/modprobe.d/dist-blacklist.conf#写入以下内容blacklistnouveauoptionsnouveaumodes......