差一点
我们就擦肩而过了
有趣
有用
有态度
我们都知道一个程序从本质上来说就是算法+数据结构,这次就以我的本科毕业设计——排课系统为例,专门讲解如何设计排课的算法和要用到的数据结构,在讲解这个算法之前,我们今天先做一些准备工作。
概述
我们先简单分析一下具体要做哪些准备工作,首先要想实现排课,必须要有排课需要用到各种数据——这些数据主要有课程、班级、学生、教师、教室,我们必定需要对这些数据进行增删改查的操作。但是,在此之前我们需要得出这些数据两两之间有什么关系,我直接给出一种可能:
- 一门课程可能对应着多个班级,一个班级可能对应着多门课程,班级和课程之间是多对多关系。
- 一门课程可能有多个教师教授,一个教师可能教授多门课程,教师和课程之间是多对多关系。
- 一个学生只属于一个班级,一个班级下有多个学生,班级和学生之间是多对多关系。
接下来我们需要考虑实体的属性(也就是对应数据表的字段),这个就比较简单了,如下所示:
课程:ID,名称,一周数量
班级:ID,名称,学生人数
学生:ID,姓名,所属班级
教师:ID,姓名
教室:ID,名称,座位数量
重点讲一下班级里面为什么要加入学生人数这个字段,因为即使不加依旧可以获取一个班级的学生人数,直接去学生表中进行查询就行了,这样做确实可以行得通,但是查询学生表需要遍历每一条学生记录,时间上不允许,因此直接空间换时间,在班级表中加入这个学生人数字段。
加入学生人数字段又面临了一个新的问题,增加删除学生的时候要确保对应班级的学生人数字段被同步——考虑以下 4 种情况:
增加学生:需要把对应的班级的学生人数+1
删除学生:需要把对应的班级的学生人数-1
修改学生(修改所属班级):需要把旧所属班级的学生人数-1,新所属班级的学生人数+1
修改学生(不修改所属班级):不用进行对应班级的学生人数的修改
需要进行的准备工作已经分析完成了,接下来直接进入技术选型部分,为了便于对这些数据进行增删改查的操作,直接使用 Django 自带的管理后台来实现就够了。但是,排课又怎么进行调用?这个我们后面再说,今天先把数据管理部分完成。
数据管理代码实现
首先,我们新建一个 Django 项目,项目下只有一个 app,和之前个人网站一样,我就不再叙述了,然后就是去编写 model 层代码,这里直接给出完整代码:
from django.db import models
from django.db.models.signals import pre_delete, pre_save
from django.dispatch import receiver
# Create your models here.
class Classroom(models.Model):
name = models.CharField(max_length=255)
seat_number = models.PositiveSmallIntegerField()
def __str__(self):
return f'({self.id}){self.name}'
class Grade(models.Model):
name = models.CharField(max_length=255)
student_number = models.PositiveSmallIntegerField(default=0, editable=False)
def __str__(self):
return f'({self.id}){self.name}'
class Student(models.Model):
name = models.CharField(max_length=255)
grade = models.ForeignKey(Grade, models.CASCADE)
def __str__(self):
return f'({self.id}){self.name}'
class Teacher(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return f'({self.id}){self.name}'
class Course(models.Model):
name = models.CharField(max_length=255)
number_per_week = models.PositiveSmallIntegerField()
grades = models.ManyToManyField(Grade)
teachers = models.ManyToManyField(Teacher)
def __str__(self):
return f'({self.id}){self.name}'
# noinspection PyUnusedLocal
@receiver(pre_delete, sender=Student)
def pre_delete_student(sender, instance, **kwargs):
grade = instance.grade
grade.student_number -= 1
grade.save()
# noinspection PyUnusedLocal
@receiver(pre_save, sender=Student)
def pre_save_student(sender, instance, **kwargs):
if instance.id:
old_grade = Student.objects.get(id=instance.id).grade
new_grade = instance.grade
if old_grade.id != new_grade.id:
old_grade.student_number -= 1
new_grade.student_number += 1
old_grade.save()
new_grade.save()
else:
grade = instance.grade
grade.student_number += 1
grade.save()
接着重点讲解最下面的两个函数,因为这两个函数是这个系统的第一个难点,因为这两个函数是实现班级表中的学生人数字段和实际的学生人数的同步。
happy father’s day
pre_delete_student
接着看一下第二个函数 pre_delete_student,这个函数很简单,就是删除一个学生,其对应的班级的学生字段-1,然后保存修改后的班级,不做详细讲解,当然也可以在使用 post_delete 信号在删除之后做出对应的修改。
happy father’s day
pre_save_student
最后看一下 pre_save_student,这里实现了两个同步——增加学生和更新学生对应班级的学生人数的同步。首先实现更新学生的班级属性时班级的变化就行,就是旧班级的学生人数-1,新班级的学生人数+1,更新的学生必然在更新之前存在 id 属性,我们只要确保当前实例的 id 属性存在就是更新,否则就是增加。接下来就是获取该学生对应的旧班级和新班级,其中旧班级是直接存储在数据库中,新班级位于内存中,就是当前学生实例的班级属性。然后如果旧班级和新班级的 id 属性不相等(意味着班级被更新),就把旧班级的学生人数-1,新班级的学生人数+1,然后把两个修改后的班级都保存到数据库即可。更新学生对班级的学生人数字段的同步逻辑讲完了,接下来看一下增加学生的实现逻辑,其实和删除学生的同步逻辑差不多,就是-1 变成+1,其他都不变。
既然上面一个函数使用 pre_delete 信号和 post_delete 信号差不多,那么在这里是不是也可以使用 post_save 信号呢?当然是不行,因为使用 post_save 信号在保存之前不会操作,保存之后才开始操作,如果保存的过程中对应的班级被更新,那么还能获取到旧班级吗?
接下来我们简单看一下效果,首先给大家看一下班级表中的数据,如图所示。
总共有两个班级,对应的学生人数字段都为 0,接下来我们去增加一个学生,其对应班级设置为班级1,增加完成之后我们继续看一下班级表,如图所示。
我们可以发现学生人数被同步了,接下来我们尝试把这个增加的学生修改一下,把他从班级1 改成班级2,改完保存之后我们再来看一下班级表的学生人数变化情况,如图所示。
很明显,更新时的同步没有问题,最后看一下删除时的同步,我们直接删除这个学生,然后看一下班级表的变化情况,如图所示。
删除时的同步同样也没有问题,明天我们批量增加各种数据,为了给排课算法进行测试。
今天的文章有不懂的可以后台回复“加群”,备注:小陈学Python,不备注可是会被拒绝的哦~!