SQL高效的计划生成算法

时间:2015-09-07 23:12:46

标签: php mysql sql algorithm search

想法

想象一下有分支的教育中心。所有分支机构都有这个教育中心的课程

分支

CREATE TABLE `Branch` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;


CREATE TABLE `Course` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `active` tinyint(1) DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
管理员生成的每个课程的每个分支中的

会议室。例如,管理员输入数学课程的房间数。系统生成3个房间。换句话说,它们受到计数的限制。

CREATE TABLE `Room` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `branch_id` int(10) unsigned DEFAULT NULL,
  `course_id` int(10) unsigned DEFAULT NULL,
  `occupied_hours` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

每个房间每天有5个教学时间。换句话说,Math-1在每个教学时间(5个)中将有1个不同的学生组。

学生 - 也按分支分组。每个学生都有每周计划(week_day_mode)来中学。

  • 一周中的第3天,第5天
  • 一周的第2,4,6天

class字段是学校(主要学校)的成绩,

CREATE TABLE `Student` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `fullname` varchar(255) NOT NULL,
  `class` tinyint(2) DEFAULT NULL,
  `branchID` int(10) unsigned DEFAULT NULL,
  `week_day_mode` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `branchID` (`branchID`)
) ENGINE=InnoDB AUTO_INCREMENT=246 DEFAULT CHARSET=utf8;

当管理员第一次注册学生时,他会选择学生想要参加的所有课程。例如,如果为此学生选择了5行StudentCourseAssoc将填充5行。在测试学生每门课程的基本知识水平之后,管理员会评估学生的聪明才智。 (+1)或"哑巴" (-1)特定课程。所以knowledge_level是学生课程连接的价值。

CREATE TABLE `StudentCourseAssoc` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `studentID` int(10) unsigned DEFAULT NULL,
  `courseID` int(10) unsigned DEFAULT NULL,
  `knowledge_level` tinyint(1) DEFAULT NULL,
  `group_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1144 DEFAULT CHARSET=utf8;

申请必须:

以下条件自动分组(可以创建新组或向现有组添加学生)每个分支的学生

  • 聪明愚蠢的学生必须在不同的小组中
  • 小组可能包含一些等级混合。所以,将9年级和10年级混在一起是可以的。并且第11名毕业(第12名意味着毕业于sql)。但不是10日至11日。 (将有2种模式:9-10,11-12)
  • 小组最多可由8名学生组成
  • 课程房间有限。因此,每个房间可能在白天只容纳5组
  • 每位学生必须在1天内选择(自己选择)每门课程

搜索符合上述条件的group后,如果找不到,应用必须创建,然后将学生分配到group。然后:

CREATE TABLE `StudentGroupAssoc` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `group_id` int(10) unsigned DEFAULT NULL,
  `student_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

CREATE TABLE `Schedule` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `group_id` int(10) unsigned DEFAULT NULL,
  `week_day_mode` tinyint(1) DEFAULT NULL,
  `hour` tinyint(1) DEFAULT NULL,
  `room_id` int(4) unsigned DEFAULT NULL,
  `teacher_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `Unique Room for exact time` (`week_day_mode`,`hour`,`room_id`) USING BTREE,
  UNIQUE KEY `Unique Group for exact time` (`group_id`,`week_day_mode`) USING BTREE,
  KEY `Unique Teacher for exact time` (`week_day_mode`,`hour`,`teacher_id`),
  KEY `room_id` (`room_id`),
  KEY `teacher_id` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

And here is fiddle to play with.

我做了什么

我想在知识评估期间将学生安排到group(现有的或创建新的)。就像,如果学生选择数学作为课程之一,当管理员评估他的数学知识并标记为肯定时,程序开始为这个学生选择正确的小组:

  • 功能标记学生知识水平
  • 检查学生的可用时间(例如,已经服用了1小时,然后他有4个小时)
  • 为搜索添加课程覆盖条件(如9-10年级或11-12年级)
  • 如果学生的每周计划中有任何可用小时组,则检查时间表

如果没有人,则尝试创建。

所以PHP表示看起来像这样

        //sets knowledge level of student
        $studentCourse->knowledge_level = intval($_POST["mark"]);

        //check hours of student, and keep only available hours
        $availableHours = array_combine(range(1, 5), range(1, 5));

        //Unsets students unavailable hours from possible hours
        if ($student->GroupRels)
            foreach ($student->GroupRels as $groupRel)
                unset($availableHours[$groupRel->hour]);

        //Checks available groups based on class coverage
        if (in_array($student->class, ['11', 'G']))
            $classCoverage = "11-m";
        else if (in_array($student->class, ['9', '10']))
            $classCoverage = "9-10";

        $availableGroups = Group::find()
            ->with("schedule")
            ->where([
                    "Group.class_coverage" => $classCoverage,
                    "Group.knowledge_level" => $studentCourse->knowledge_level,
                    "Group.participiant_count<8",
                    "Schedule.hour" => $availableHours,
                    'Schedule.week_day_mode' => $student->week_day_mode
                ]
            )->all();


        if (count($availableGroups) > 0) {
             //Selecting one of groups
             //adding row to StudentGroupAssoc
            //adding row to Schedule
        } else {
            $group = new Group();
            $group->branch_id = $student->branchID;
            $group->class_coverage = $classCoverage;
            $group->course_id=$studentCourse->courseID;
            $group->knowledge_level=$studentCourse->knowledge_level;
            $group->save();
            ...
            //adding row to StudentGroupAssoc
            //adding row to Schedule


        }

问题是

理论上,我所做的就像买飞机票一样。没有错误,必须工作,但它没有效率和最佳。必须以最有效的方式满足所有分组条件:最小化群数和满足有限的房间数政策。这种方法很快就会产生很多不适合可用时间的小组。

当我逐个学习数小时(在评估过程中)时,获得真正有效的结果变得越来越难。由于学生的限制,没有找到小组而不能因为房间限制而无法创建新小组的机会正在上升和下降。

您建议在每个房间使用每小时一次?

更新

根据@norbert_van_nobelen的回答,我创建了“假”&#39;小时表和以下视图,以获取每个学生的所有可能的小时 - 课程组合列表。

hours真实的计划时间 hours_available是二进制开关。 所以在实际代码中我们添加一个where子句:WHERE hours_available = 0只获得我们想要计划的小时数:

SELECT
    `s`.`id` AS `student_id`,

IF ((ifnull(`sch`.`hour`, 0) > 0), 1, 0) AS `hour_available`,
 `d`.`hours` AS `hours`,
 `sca`.`courseID` AS `courseID`,
 `sch`.`room_id` AS `room_id`,
 `sca`.`knowledge_level` AS `knowledge_level`,
 (
    CASE
    WHEN (
        (`s`.`class` = 9)
        OR (`s`.`class` = 10)
    ) THEN
        '9-10'
    WHEN (
        (`s`.`class` = 11)
        OR (`s`.`class` = 12)
    ) THEN
        '11-12'
    ELSE
        '??'
    END
) AS `class_variant`
FROM
    (
        (
            (
                (
                    `dummy_hours` `d`
                    JOIN `Student` `s`
                )
                LEFT JOIN `StudentCourseAssoc` `sca` ON ((`s`.`id` = `sca`.`studentID`))
            )
            LEFT JOIN `StudentGroupAssoc` `b` ON ((`s`.`id` = `b`.`student_id`))
        )
        LEFT JOIN `Schedule` `sch` ON (
            (
                (
                    `sch`.`group_id` = `b`.`group_id`
                )
                AND (`d`.`hours` = `sch`.`hour`)
            )
        )
    )

使用此视图可提供当前情况的完整场景。但我仍然无法弄清楚算法

  • 将学生分组
  • 将小组放在房间

以最有效,最优的方式创建最少的群组数。

有什么建议吗?

2 个答案:

答案 0 :(得分:6)

这个答案只是作为计划部分的解决方向,而不是100%的好解决方案:

您创建的内容需要循环才能满足所有条件。

为了更快地解决这种情况,在矢量中工作是可行的,而在矢量中所有位置都由0(可用)和1(采用)表示。

所以学生/数学-1问题:

假设有2个房间和3个小时:每个房间的数学-1矢量是:

CREATE TABLE room(
room_id INT,
space TINYINT DEFAULT 0,
hour INT DEFAULT 1
);

CREATE TABLE student(
student_id INT,
space TINYINT DEFAULT 0,
hour INT DEFAULT 1
)

基本上(我至少)不关心某个房间是否可用,只要1可用: 因此,在这种情况下,AND per index可能是可用性的答案(记住:0可用):

第1室:[1 0 0]    2号房间:[0 0 0]    房间结果:[1 0 0] AND [0 0 0] = [0 0 0]

因此,AND可以判断第一个小时是否仍然可用。

如果您现在将其与具有可用小时数的学生(此示例中仅为3)合并:

学生A:[0 0 1]    房间结果:[0 0 0]    对于此操作,学生使用OR与房间匹配:      [0 0 1]或[0 0 0] = [0 0 1]

所以学生A会匹配房间结果。

在SQL中:数据模型(部分:缺少是课程匹配): 表室:

INSERT INTO room VALUES (1,0,1);
INSERT INTO room VALUES (1,0,1);
INSERT INTO room VALUES (1,0,1);
INSERT INTO room VALUES (1,0,2);
INSERT INTO room VALUES (1,0,2);
INSERT INTO room VALUES (1,0,2);
INSERT INTO room VALUES (1,0,3);
INSERT INTO room VALUES (1,0,3);
INSERT INTO room VALUES (1,0,3);

所有数据已完整插入表中:在这种情况下,1个房间,3个小时,3个位置。

INSERT INTO student VALUES(1,0,1);   
INSERT INTO student VALUES(1,0,2);   
INSERT INTO student VALUES(1,1,3);   

学生有:

SELECT room_id
FROM room a
INNER JOIN student b ON a.space=b.space AND a.hour=b.hour;

所以学生只能在前两个小时内上学。

现在从查询中获得结果:

<?xml version="1.0" encoding="UTF-8"?>
<a>
    <b key="x"/>
    <b key="y"> text></b>        
    <b key="z"><p>This contains HTML Code.<br/><br/>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. <br/><br/>At vero eos et accusam et justo duo dolores et ea rebum. <br/>///<br/>///Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.///<br/> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. <br/>At vero eos et accusam et justo duo dolores et ea rebum. <br/>Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p></b>
</a>

这个结果只需要分成最多8个组,其中它是SQL部分的结尾和另一个编程语言的时间。

此模型可以使用日期进行扩展,但只使用几小时和工作日(工作日可用性再次为0或1)时效果最佳。

正如我所说:这是一个概念/想法,而不是100%的解决方案,所以它需要工作才能使用它......

答案 1 :(得分:1)

有趣的问题,对我来说,我会对一种方法提出建议,因为我的大脑不会在数学方面构建逻辑问题,但我被看作显示智力超出美发,所以我去。

我可以遵循提议的缺乏收缩,这让我想到了线性问题/编程,它们也需要精确的约束来计算最优。而且我们可以通过先将它除以2来将矩阵计算减半,然后在下半部分或上半部分中搜索结果,因为它不能同时在两者中。但是没有什么可以削减一半所以我认为更合理地假设必须有一个真实的生活在这里有意义或它不会起作用或加起来很快就是我的术语: d

所以我现在建议这种方法有逻辑:从入门课程到考试,课程是线性的。因此,没有参加过入门课程的学生可能会再次参加,因为它只是愚蠢的(爱因斯坦等人:)。因此,参加math1的每个学生都可以被排除在该课程之外。因此,如果我们逐步采用数学1到5以及必须参加所有课程的规则,其中课程水平与学生水平相差不超过-2,等于给定学生以线性方式参加的课程,那么所有参加过课程1的学生都可以被排除在该课程之外。因此,对于数学2,只有具有水平或课程的学生,0和1可以参加。对于数学3,1级或2级的学生可以参加。因此,如果我们开始创建最大的学生群体,可以参加任何课程并开始直接削减智能和愚蠢,以节省时间,削减毫无意义的选项,因为4级学生永远不会参加同级数学课程0级,1名学生?

或类似的东西。我正在为这个图表工作,但它现在看起来更像我的邻居的车库所以不要期望太多我猜...