第六次作业——结对编程第二次
结对情况
自己
- 516
- 佳莹
队友
- 528
- 媛
- 博客地址链接:[http://www.cnblogs.com/su-yuan/p/7673876.html]
项目链接 :
Github地址:[https://github.com/outblue/students-departments]
设计说明
-
API接口设计
-
类图
-
匹配算法设计
-
1,思路设计
我们的算法设计是基于学生的“绩点”“志愿”“tag”和部门的“活动时间”所组成的。
经过讨论过后我们认为能够影响一个部门录取新成员的主要因素大致为这个学生的绩点,这个学生所报的志愿部门数(报的越多的学生能够分配给该部门的精力大大受限),和他们之间勾选的tag之间的重合度。
除了这三个之外,我们还加了部门之间活动时间的重合度,因为假如这个学生他报了两个活动时间相同的部门,他又同时中选,那就会大大影响他参与部门之后各种活动工作的次数了。
综合这些考虑,我们决定了【绩点、志愿数、Tag匹配度、各部门时间重合度】这四个影响因素,通过计算在这四点的情况下一个学生对于他所报名的部门的综合得分来匹配部门要招收的人。
各因素在综合得分中所占的权重根据其对录取的影响度来决定,参考了这篇文章[https://wenku.baidu.com/view/cbd00f2c647d27284b73515a.html?qq-pf-to=pcqq.c2c]中的第三个方案,也就是“对偶比较法”来决定。
最终公式如下:
- 绩点(gpa)
- 志愿数(aspi)
- tag匹配度(tag)——学生自己的tag与他所报名的部门的tag重合的数量
- 各部门时间重合度(time)——这个可以说是对后期影响非常大的一个因素了,且是消极因素,所以该学生所报名的部门之间如果活动时间有重复,就乘上3设置为负数。
每个社团加权计算出报名学生的得分后,这份综合得分会和这个学生的ID一起存进部门的候选名单里,然后依据得分高低和他们所计划招收的人数来选取学生,得到最终的新成员列表。
-
2,流程图
-
测试数据的生成
根据需要,一共设定了十个标签和十一个可能的部门活动时间,具体内容如下:
Tag = {study,read,exercise,relax,science,art,music,history,interact,write};
Actime = {Monday-evening;Tuesday-evening;Wednesday-evening;Thursday-evening;Firday-evenday;Saturday-morning;Saturday-afternoon";Saturday-evening;Sunday-morning;Sunday-afternoon;Sunday-evening};
部门
1,department_no——部门ID:第一个部门的ID为“D0000”,之后的部门以这个格式为标准,尾数依次增加。若有20个部门,则最后一个部门的ID为“D0019”。
2,member_limit——招收人数:题目要求在[0,15]之间随机,因此不排除出现0的情况。
3,tags——部门标签:在十个标签中随机得出,数量在[1,5]之间。
4,event_schedules——部门活动时间:在十一个时间段里随机,数量在[1,3]之间。
学生
1,student_no——学生ID:第一位学生的ID为“S0000”,之后的部门以这个格式为标准,尾数依次增加。若有200个学生,则最后一位学生的ID为“S0199”。
2,student_gpa——学生绩点:绩点最高为5,在[0,5]之间随机,保留两位小数。
3,tags——学生标签:在十个标签中随机得出,数量在[1,5]之间。
4,student_aspirations——学生志愿:不为0,不超过5,在1~5里随机。
-
评价自己的算法
我所编写的代码就结果而言不是个很好的算法。一直到最后提交作业的时间来临,我的最后一组数据【5000学生100部门】也没能跑出结果来。其他测试的情况也是在费时上比较长。
这个算法的设计总体上可以说是从部门的角度出发的,因为在学生与部门的匹配关系间,我们认为占主动权的是部门。因此是只要有一个学生报名了这个部门而这个部门想要招收的人数也不为0,就一定不会出现该部门没有新成员的现象。
而相对的,学生的情况就比较严峻,部门挑人是按分数从高到低来算,有的部门甚至要招收的人数为0,也就是不收新成员,这使得僧多粥少,很多学生无法加入部门。
学生的中选率过低,个人分析后认为是算法中学生绩点的影响过大。不是说它的权重大,而是在最为重要的tag数完全随机的情况下,他们跟部门tag能够重合的比例过低,导致不受外力影响的绩点因素的影响被放大。但如果是结合实际生活中的情况的话,学生一般情况下不会选择与自己勾选tag完全不契合的部门,也就不会产生以上的偏差影响,再加上部门数量有限,生活中能够加入部门的学生本来就只是一小部分,所以我认为这个算法本身还是比较合理的。
关键代码
结构体定义
多出的一个Applylist结构体是为了将学生编号和他们自己的分数绑定,且便于大小排序。
const int Numstu = 200;
const int Numdep = 20;
struct Applylist //部门候选成员清单,存储申请人的分数和ID
{
string ID;
double csm;
};
struct Departments
{
string depID; //部门ID
int need; //部门招收人数
vector<string> dep_tag; //部门tag
vector<string> dep_time; //部门活动时间
vector<string> ID_list; //申请人ID列表
vector<double> csm_list; //申请人分数列表
Applylist list[Numstu]; //存储申请人的ID和他所对应的分数
int num_mem; //报名的人数
vector<string> apply; //中选的学生ID
}depart[Numdep];
struct Students
{
string stuID; //学生ID
double gpa; //学生绩点
vector<string> stu_tag; //学生tag
vector<string> stu_aspi; //学生志愿
int num_aspi; //选取该学生的部门数
vector<string> dep_join; //学生加入的部门
}stu[Numstu];
部分随机数据生成
除去编号是按照顺序来的,学生和部门其余的值全部都是取随机数。
//部门所需人数随机取值
for(i=0;i<Numdep;i++)
{
int p=rand()%15;
depart[i].need=p;
}
//部门tag随机取值
for(i=0;i<Numdep;i++)
{
int p=rand()%5+1;
for(j=0;j<p;j++)
{
int q=rand()%10;
string temp;
temp = change_tag(q);
depart[i].dep_tag.insert(temp);
}
}
//部门活动时间随机取值
for(i=0;i<Numdep;i++)
{
int p=rand()%3+1;
for(j=0;j<p;j++)
{
int q=rand()%9;
string temp;
temp = change_actime(q);
depart[i].dep_time.insert(temp);
}
}
部分分配算法
分别计算四个因素的值,再将它们加起来形成总分,然后部门处会对他们所持有的列表进行分值由大到小的排序。
//计算学生所报名的志愿数量,越多得分越少
int point_num_aspi(Students stu)
{
int n, point;
n = stu.stu_aspi.size();
point = 6-n;
return point;
}
//计算学生tag与部门tag重合的数量及其在该项的得分
int point_num_tag(Students stu,Departments dep)
{
int n=0;
for (vector<string>::iterator it_s = stu.stu_tag.begin(); it_s != stu.stu_tag.end(); ++it_s)
{
for (vector<string>::iterator it_d = dep.dep_tag.begin(); it_d != dep.dep_tag.end(); ++it_d)
{
if(*it_s == *it_d) n++;
}
}
return n*5;
}
//计算学生在其所报名的部门的综合得分
void calculate(Students stu)
{
int i = 0;
for (vector<string>::iterator it_s = stu.stu_aspi.begin(); it_s != stu.stu_aspi.end(); ++it_s)
{
double sum = 0;
int k;
k = get_department(*it_s);
sum += stu.gpa * 0.18; // 绩点权重为0.18
sum += point_num_aspi(stu) * 0.25; //志愿权重为0.25
sum += point_num_tag(stu ,depart[k]) * 0.32; //重合的tag数得分权重为0.32
sum += stu_dep_time(stu ,depart[k]) * (-3) * 0.25; //重合的部门活动时间得分权重为0.25
depart[k].csm_list.push_back(sum);
depart[k].ID_list.push_back(stu.stuID);
depart[k].num_mem += 1;
}
}
//该部门报名清单排序
Departments list_rank(Departments dep)
{
int i,j;
Applylist app;
for(i = 0; i < dep.num_mem-1 ; i++)
{
for(j = 0; j < dep.num_mem-1-i; j++)
{
if(dep.list[j].csm < dep.list[j+1].csm)
{
app = dep.list[j];
dep.list[j] = dep.list[j+1];
dep.list[j+1] = app;
}
}
}
return dep;
}
运行及测试结果展示
-
测试结果概况
前三组数据测试成功,第四组数据仅成功生成随机数部分。
三次测试的结果如下:
总人数 | 中选学生数 | 落选学生数 | 总部门数 | 新成员部门 | 未招收成员部门 |
---|---|---|---|---|---|
200 | 105 | 95 | 20 | 19 | 1 |
500 | 142 | 358 | 30 | 28 | 2 |
1000 | 279 | 721 | 50 | 47 | 3 |
由上表易见学生的中选度较低,而部门基本上不会出现无学生可收的情况,仅有的极少数缘由还是因为一开始他们自身随机出的要招收成员数就是0。
-
测试数据链接
Github地址:[https://github.com/outblue/students-departments/tree/master/tests]
-
200学生和20部门
-
随机数据生成
-
- ####分配结果
-
500学生和30部门
-
随机数据生成
-
- ####分配结果
-
1000学生和50部门
-
随机数据生成
-
- ####分配结果
-
5000学生和100部门
-
随机数据生成
-
- ####分配结果
暂无
-
效能分析
由上图易看出函数calculate所占的时间比是最大的,这个函数也正是我设置来计算学生总分数的函数,看来我在这个部分还有很大需要改进的地方,应当要有更为简洁的方法来计算。
遇到的困难及解决办法
要说起这次所遇到的困难那真是一把泪,从json的使用到随机数据输出再到分配算法,几乎每一块都令我非常抓狂。
一开始我接触json,意识到这是从来没有接触过的内容。网上关于json有非常多的帖子,我最先搜索的时候,jsoncpp看到的次数最为频繁。于是我就选择学习它。但不知道是我个人理解力的因素还是搜索的时候方向不对,虽然帖子很多,但讲得通俗易懂的却几乎是没有。我看了很长都没搞懂到底要怎么用,平白浪费了很多时间。后来我改为学习cjson,这个跟jsoncpp相比起来要容易学一点,它在使用的时候只用在项目里添一个cJSON.c和cJSON.h就可以,使用的起来的语句也比较容易看明白。
随机数据比较简单一些,排去后头的输出到json文件,它处理起来最大的麻烦之处就在ID的设置了。考虑到后面会需要测试较大的数据,我就把ID的长度拉长了一些。
分配算法的计算比较繁琐,而且学生和部门之间结构体的联系很多,逻辑之间看久了容易乱,在处理时费了不少精力。
现在想来最大的困难还是对json文件的读取和输出。在编程的时候也困扰了非常久该如何读取另一工程所生成的文件,最后是硬通过绝对路径打开的。还有因为我个人的审题失误导致没有做的API接口,这个问题我今天才发现,所以没能来得及解决,查了一下也不太明白到底应该怎么做,之后会向同学请教一下。
本次作业从各种方面来说对我都是一种很大的挑战,因为一些原因,代码编写的部分是由我完成的,所以在很多细节的地方比较粗糙,整体架构的设计安排上也有非常多的不足。程序运行起来速度太慢,最后一组数据没能跑出来非常可惜。但虽然完成得并不算出色,我自身还是挺满足的。接触了从没有使用过的东西,尝试了一些使用得还比较生疏的代码,最后的测试也运行成功了一大半。虽然代码本体还有很多瑕疵,完成它也很辛苦,但总算是没有白费。
对队友的评价
我的队友是一个脾性很好的人。我本身性格有点起伏大,有时候敲代码敲久了又总不能成功运行,或者学习新技术学不会的时候就会说话比较僵硬,但她从来没有同我计较过。
要说哪里需要改进的话,就是希望她性格能够更加坚韧一些吧。
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 25 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 25 |
Development | 开发 | 2080 | 2425 |
· Analysis | · 需求分析 (包括学习新技术) | 400 | 535 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0 | 0 |
· Design | · 具体设计 | 180 | 190 |
· Coding | · 具体编码 | 1000 | 1140 |
· Code Review | · 代码复审 | 0 | 0 |
· Test | · 测试(自我测试,修改代码,提交修改) | 500 | 560 |
Reporting | 报告 | 145 | 210 |
· Test Report | · 测试报告 | 120 | 180 |
· Size Measurement | · 计算工作量 | 5 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 25 |
合计 | 465 | 2660 |
学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 803 | 803 | 9 | 9 | 学习了json的使用,还有分配原则的相关尝试 |