您的位置:首页 > 其它

软件工程实践2017结对项目——第二次作业

2017-10-09 00:21 344 查看

作业链接
成员:
031502541 张昭锡
031502512 黄世辉
Github:
传送门

一、问题描述

实现一个智能自动分配算法,根据输入信息,输出部门和学生间的匹配信息(一个学生可以确认多个他所申请的部门,一个部门可以分配少于等于其要求的学生数的学生) 及 未被分配到学生的部门 和 未被部门选中的学生。
提供输入包括:

  • 20个部门
  • 部门编号(唯一确定值),字符;
  • 各部门需要学生数的要求的上限,单个,数值,在[10,15]内;
  • 各部门的特点标签,多个(两个以上),字符;
  • 各部门的常规活动时间段,多个(两个以上),字符。
  • 300个学生
  • 学生编号(唯一确定值),字符;
  • 学生空闲时间段,多个(两个以上),字符;
  • 兴趣标签,多个(两个以上),字符(学生的兴趣标签一定是所有部门特点标签其中的一个)
  • 每个学生有不多于5个的部门意愿

二、数据生成

    数据生成是先初始化学生和部门的信息,然后通过随机以及枚举来生成时间和标签。在数据方面,主要控制了学生空闲时间段和部门常规活动时间段以及学生意愿,避免80%的学生选择了20%的部门,数据太过于集中而导致的不均匀。时间方面,控制了学生和部门在工作日的空闲时间段,不至于过多,上限为三个时间段,时间段不超过两个小时,而周末的上限不超过五个时间段,并且各时间段最晚不超过22:00。
以下是每个部门被学生作为其意愿的次数:

一组“最好”(?)的数据:data.txt
随机出来的数据好像分布都挺均匀的,生成结果在符合我们订下的依据也差不多..........

三、匹配算法

数据建模
学生角度
学生依据个人给出空闲时间段、兴趣标签以及按优先级给出其意愿部门。对于兴趣标签可看作是学生技能,作为闪光点,加大被意愿部门挑选的概率,而部门在学生意愿中的顺序也表现了学生希望加入部门的不同强烈程度。
部门角度
部门依据部门情况给出人数上限、常规活动时间、部门特点标签。在实际情况,部门肯定较为优先考虑自己部门是学生意愿中优先级高的、学生空闲时间与部门常规活动时间正好对得上且学生技能或兴趣与部门特点一致的学生。

由于学生偏好等个体差异以及部门依据部门实际情况挑选学生,最后会造成分配的不均匀,因此要考虑控制各方面因素,实现学生与部门的最大匹配。而我们讨论得出的匹配依据是尽可能让每个学生都能加入到自己意愿的部门,而有多于部门上限人数学生选择它时,它尽可能选择满足自己部门情况的学生。

匹配程序
    匹配算法的思路是基于稳定婚姻算法来进行的。稳定婚姻算法是一对一的,但是在上述需求中有一个明显的不同点就是可以多对多,一个部门可以选择多个学生,一个学生可以加入多个部门。因此需要在这个算法基础上稍加改造。另一方面,学生信息中有多个指标,部门信息也有多个指标,如何去处理这些指标问题,我们采取的策略是依据学生与各意愿部门的信息匹配度各给出一个分值,然后依据这些分值去进行匹配。而关于分值在各指标分配的策略,我们最后订下的规则是:

  • 总分值100分
  • 社团优先度(这个社团在学生意愿中排第几位)占比40%,根据意愿顺序,第二位扣5分,第三位扣10分,依此类推
  • 兴趣标签符合度(学生兴趣与部门特点标签相同的个数)占比30%,分五档,一个标签相同得6分,最多得30分
  • 时间重叠度占比30%,以半小时为时间段单位,分五档,一个时间段单位重叠得6分,最多得30分

    以上规则的制订所考虑的现实因素:学生角度,部门在学生意愿中的排序可以说体现了学生所希望加入的部门的强烈程度,因此在考虑的三个因素之中占比最大,而在兴趣标签符合度和时间重叠度上占比相同。这两个因素是站在部门方面的考虑,是部门挑选部员的两个重要因素,因此将之比例设置相同。其实,在一开始三个因素的占比按照学生和部门分别占比50%,但是得出的结果基本是按照不管兴趣标签度和时间重叠度上是否符合,这个学生第一意愿是哪个部门就会入选。最后,我们讨论了一下,这两个因素在实际中,学生与部门符合度不可能达到很高的百分比,因此按照这样的学生角度和部门角度的对半占比有失合理。另外,时间重叠度上以半小时为时间段单位是考虑到实际中部门的常规活动时间例如议会等都在一两个小时以内且一周也不可能有过多的活动。

四、代码规范

我们这次结对编程所选取的语言是C++,因此我们沿用了之前大一时候学长给的一份C++代码规范:C++编码规范
另外,对于命名规则我们选用了:函数名使用驼峰命名法,变量名使用下划线命名法,并且命名要让人见字知意,有些无法达到的要配以注释说明。
文档其中的一些规则

4.1规则:程序块要采用缩进风格编写,缩进的空格数为4个。
4.2规则:缩进或者对齐只能使用空格键,不可使用TAB 键。
4.3规则:相对独立的程序块之间、变量说明之后必须加空行。
4.6 规则:if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加括号{}。
4.7规则:代码行之内应该留有适当的空格,采用松散方式编写代码
5.2规则:函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值等。

示例代码如下:

/*************************************************
Description:    社团依据学生对于填报社团的优先度,时间重叠度,
兴趣标签符合度对填报的学生给出一个满意度,满意度总分100分
Input:          学生,部门以及这个部门是学生的第几个意愿
return:         社团对学生的满意度
*************************************************/
double getPoints(Student stu, Dept dept, int pos)
{
double points = 0;

//社团优先度占比40%,社团优先级第一则全得,其他依次扣五分
points += 100 * 0.4 - (5 * pos);

//兴趣标签符合度占比30%,共分五档,一个标签相符得6分,
int identical_tags_cnt = getIdenticalTags(stu, dept);

points += 6 * (identical_tags_cnt > 5 ? 5 : identical_tags_cnt);

//时间重叠度占比30%,共分五档,以半小时为单位,一个单位相重叠得6分
int overlay_time_cnt = getOverlayTime(stu, dept);
points += 6 * (overlay_time_cnt > 5 ? 5 : overlay_time_cnt);

return points;
}

/*************************************************
Description:    利用稳定婚姻算法的变形实现部门要求与学生意愿的匹配
Input:          学生总人数,部门总个数
return:         无
*************************************************/
void distribute(int stu_size, int dept_size)
{
//稳定婚姻算法的应用
//初始学生全部入队列
queue<Student>que;
for (int i = 0; i < stu_size; i++)
{
que.push(stu[i]);
}

//稳定婚姻算法应用变形
while (!que.empty())
{
Student& student = stu[getStuIndex(que.front().stu_no, stu_size)];
que.pop();

//考虑学生student的第pos个志愿
Dept& department = dept[getDeptIndex(student.dept_no[student.pos++], dept_size)];

//如果部门还有人员名额,则学生直接中选
if (department.limit > department.choose)
{
department.stu_no.push_back(student.stu_no);
department.choose++;
student.bechosen = true;
}
else
{
//如果没有名额的话,找出部门挑选学生中的最低评分的人,与之比较
string min_stu_no = "";
int pos = -1;
double min_points = 100.0;
for (int i = 0; i < department.choose; i++)
{
Student tmp = stu[getStuIndex(department.stu_no[i], stu_size)];
if (tmp.stupoint[department.dept_no] < min_points)
{
min_points = tmp.stupoint[department.dept_no];
min_stu_no = tmp.stu_no;
pos = i;
}
}

//部门对于学生的评分比这个部门中中选的学生的最低评分还低,等待下一轮
if (student.stupoint[department.dept_no] < min_points)
{
//学生的部门意愿还没有考虑完毕,放入下一轮
if (student.pos < (int)student.dept_no.size())
{
que.push(student);
}
}
else
{
//部门对于学生的评分高于这个部门中中选的学生的最低评分,替换到这个最低评分的学生
Student& min_stu = stu[getStuIndex(min_stu_no, stu_size)];
min_stu.bechosen = false;
if (min_stu.pos < (int)min_stu.dept_no.size())
{
que.push(min_stu);
}
department.stu_no[pos] = student.stu_no;
student.bechosen = true;
}
}
}
}

五、结果评估

对于助教的input_data.txt数据,程序运行的结果:out_put.txt,可以看到所有部门都招满了,从另一方面说明了学生尽可能加入自己志愿的部门(不管是第一或第二),符合我们订下的指标。

未招满部门 招满部门 部门招收总人数 部门非招收学生的第一志愿
0 20 235 37
0 20 241 24
0 20 248 27
0 20 244 34
0 20 239 28

以上是测试的五组数据,从中可以看到部门都能招满人,并且部门招收的学生并不是每个部门都是招收学生的第一志愿,从侧面也说明了除了考虑社团优先度外,在标签符合度和时间重叠度也考虑进去,从而佐证程序一定程度的正确性。
另外,从这些测试结果可以看出,部门在学生意愿的优先度还是挺重要的,只要自己意愿的部门在填报时不排的过于后面,一般都会被选上。当然,我们也考虑过降低社团优先度的占比,提高时间和兴趣标签两个因素的占比,但是又想到不知道其他同学的策略是如何的,所以最后,还是坚持下来这种分配策略。

六、结对感受

    本次结对整体来说还算融洽,但是效果可能没有第一次那么好,可能是因为国庆各自回家的原因。在放假之前我们初步讨论了整个作业的要求,理顺了大致方向,将这次的开发分为了三步:数据生成、数据解析、匹配算法。虽然是放假之前就讨论了这些东西,但是实际开始编码假期已经过了一半了。最后,我们的策略是大前提下一起讨论,然后独自开发,我的队友负责数据生成部分,而我负责数据解析和匹配算法。虽说是独自开发,但是整个过程还是遇到了很多需要讨论的地方,比如在上述提到的“依据学生与各意愿部门的信息匹配度各给出一个分值”其中的就各因素的占比细节我们利用QQ通话就聊了接近一小时,最后得到的结果也只是先订下一个比例,然后根据数据得到的结果再看看是否更改比例。当然,在一起讨论的过程中,也会互相补充一些各自忽视掉的东西。这也是结对带来的另一个好处吧。对于代码托管方面上,由于我们两人都是第一次使用Github中的collaborator功能,因此在使用git的时候也出了一些问题。因为git的不熟练,每次看到git的控制台报错,心里总是很慌,但是通过搜索解决了问题又会有一种成就感涌上心头。
    总的来说,通过这次的实践又学到了一些东西。虽然说这些东西,你自己平时也可以去学习,但是没有压力的话自己总是会一拖再拖,最后迟迟什么也没学到。软工实践课就像一个契机,就像打开了一个新世界大门(当然,话虽然这么说,但是其实一直以来都很担心哪次作业无法完成=_=)。另外,说一点结对中遇到的问题吧,也给以后留个警戒。结对中沟通交流很重要,不然说一大半,听着很有道理,迷迷糊糊就答应了,最后却来返工挺痛苦的。结对队友之间的分工应尽可能明确界限,不然有一些模糊边缘,你觉得队友可能做了,队友觉得你也可能做了,结果大家都没弄好。另外,如果希望队友完成什么任务,能尽可能描述清楚。经验教训就是,我还在研究匹配程序,而队友输出程序已经写得差不多,我就让他研究下github的团队协作。然后可能也没表述清楚,最后两个人一起学习返工。最后,希望自己继续加油吧。

七、参考资料

什么是算法:如何寻找稳定的婚姻搭配
优化的Gale-Shapley算法在学生选课问题中的应用

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: