软工实践:结对项目第二次作业

项目的Github链接:matchStu&Dept

作业原文:结对项目第二次作业

1.结对成员

  • 031502142 张鹏
  • 031502216 江郑

2.数据生成部分

2.1 策略

部门:

  • 部门编号:从D001到D020顺序生成,保证有20个部门
  • 纳新总数:随机,范围[10, 15],整型
  • 特点标签:约定总的标签合集,从中随机选取4-6个,保证两个以上
  • 常规活动:每周随机选取3-4天,再从约定的时间段合集随机选取一个,总的保证两个以上

学生:

  • 学生编号:从031502001到031502300顺序生成,保证有300个部门
  • 空闲时间:每周随机选取3-4天,再从约定的时间段合集随机选取两个,总的保证两个以上
  • 兴趣标签:从意愿部门的特点标签合集里取,约定随机取3-4个,保证两个以上
  • 意愿部门:随机意愿部门的数量,范围[1,5],再随机取出相应的个数,优先级即不重复随机数出现的顺序

考虑因素:

  • 数据的合理性:对于作业要求中提到的“贴出你们生成的一组最“好”的数据”,我们的理解是生成数据的合理性,比如说学生的空闲时间有“8:0010:00”也有“10:0012:00”,就是不大合理的数据。学生的兴趣标签来自意愿部门的标签合集,也是我们数据合理性的体现。其它的一些时间段的限定,是我们根据个人感受决定的,像部门每天的常规活动时间只有一次之类,虽然可能不具有普遍性,但在一定程度上也是合理的。
  • 实现的简单性:对于上面提到的空闲时间“8:0010:00”和“10:0012:00”同时出现的问题,我们的解决办法很简单,设置不相邻的时间段合集,从中随机选取出来的就不可能会出现这个问题,实现起来也比较简单。

2.2 实现相关

实现的技术含量不高,没有什么特别机智的做法,稍微提一下学生意愿部门的随机生成。为了随机出不重复的意愿部门,设置数组flg[20],初始化为全0,比如随机出1,若flg[1]为0,则将其置1,若flg[1]为0,则表示1已经出现,需要再次随机。此外每次随机出来也要记录,以便之后确定意愿部门的优先级。还有就是按Json格式输出到文件,我们用的是C语言,使用了cJSON现有的函数,而且是队友完成的,不再展开。

2.3 一组最“好”的数据:input_data.txt

3.智能匹配部分

3.1 策略

总体结构

  1. 从input_data.txt读取json格式的字符串
  2. 遍历学生,尝试匹配每个意愿部门,并保存相关信息
  3. 按json格式输出到out_put.txt文件

核心匹配

  • 遍历学生
    • 遍历意愿部门
      • 尝试匹配学生空闲时间和部门活动时间
        • 匹配成功
          • 计算学生和部门相同的标签个数
          • 检查部门是否已经招满
            • 未招满,直接进入,保存有关信息,跳出遍历下个意愿部门
            • 招满,考虑淘汰,比较相同标签个数来淘汰学生,保存有关信息
        • 匹配不成功,继续下一个部门

3.2 实现相关

  • 整体读取文件保存至字符串:这个的实现问题不大,主要参考博客: C、C++一次将整个文件读入内存,但是“printf("%s",jsonStr)”测试时最后总是会输出一些不该存在的奇怪字符。想起原来无聊入门python时,教程说不要用记事本来写“.py”,因为“记事本会自作聪明的在文件开始的地方加上几个特殊字符从而导致莫名其妙的错误”。于是,我试着用VSCode来新建.txt文件,不过结果是一样的。后来证明是对后面的解析并没有影响,赶着deadline没去深究,但还是想提一下,万一有人知道呢。

  • 学生空闲时间和部门活动时间匹配:这个主要是由队友负责的,但是大致知道怎么实现。时间的格式都是固定的,形如“Mon.8:00~10:00”,提取出需要的信息不难,就是麻烦了点。星期匹配的话,提取出来小时数乘60再加上分钟数,看看会不会匹配就好。

  • 部门纳入相关信息保存:这是我自己最得意的部分,用链表来实现,写的时候改得怀疑人生,经常没处理好指针。具体来说,分别定义部门和学生的结构体,部门的里面有部门编号,纳新人数,已收人数和指向学生结构体的指针,学生的里面有学生在遍历时出现的顺序(不是学生编号,是考虑到没说编号是顺序的,unlucky_student时要用到,下面再提)以及和部门相同的标签个数。开个20大小的部门结构体数组,遍历学生的时候可以纳入就malloc一个学生结构体,初始化有关信息,接到相应部门后面。这个接到部门后面也是有处理的,按照与该部门相同的标签个数从小到大,越大越远离头,相等的越老越远离头,这是为了可能的淘汰做准备。指针移来移去,改bug也改了好久,打出来的时候还是挺兴奋的。

  • 学生进入部门相关信息保存:对于unlucky_student的处理,我开了一个数组“char *student[300]”,遍历学生的时候顺便给它初始化学生的学号。另外还有一个flg[300]数组,用来标记没被纳入的学生,遍历上面的部门结构体及其学生的时候可以初始化。最后根据flg[300],把没被纳入的学生学号放到unlucky_student里。

  • 按json格式输出到文件output_data.txt:cJSON的使用还是很容易上手的,这个问题也不大,具体代码不是很长,在下面的代码规范中贴出。

3.3 代码规范

每次都是赶着deadline,最后一天打代码打得飞起,这次还是用C,结果是写了好长一大串下来,最后丑得自己都不想看自己的代码。好在成功打了出来,最后还有点时间,耐着性子改得规范点,主要也就是变量名字,换行,注释啥,可读性好点。还有就是把一长串的代码分成几个.h和.c,看起来好看多了,参考资料:如何组织好 C 的头文件

生成最终Json字符串的代码:

for(i = 0; i < 20; i++)
{
	sp1 = department[i].next;
	/*生成unlucky_department*/
	if(sp1 == NULL)
	{
		cJSON_AddItemToArray(unluckyD, cJSON_CreateString(department[i].d_no));
	}
	else
	{
		/*生成admitted*/ 
		cJSON *admittedDno, *admittedS;
		admittedDno = cJSON_CreateObject();
		admittedS = cJSON_CreateArray();
		cJSON_AddItemToObject(admittedDno, "member", admittedS);
		while(sp1 != NULL)
		{
			cJSON_AddItemToArray(admittedS, cJSON_CreateString(student[sp1->order]));
			flg[sp1->order] = 1;
			sp1 = sp1->next;
		}
		cJSON_AddStringToObject(admittedDno, "department_no", department[i].d_no);
		cJSON_AddItemToArray(admitted, admittedDno);
	}
}
for(i = 0; i < 300; i++)
{
	if(!flg[i])
	{
		/*生成unlucky_student*/
		cJSON_AddItemToArray(unluckyS, cJSON_CreateString(student[i]));
	}
}
/*生成输出json字符串*/ 
char *out = cJSON_Print(rootOut);

3.4 结果评估

复制作业中给的样例input_data.txt来运行,运行结果如下截图:
软工实践:结对项目第二次作业

没去写专门的评估程序,稍微数了下unlucky_student有180多个,总共就300个,所以效果不是很好吗,我觉得策略挺合理的呀。充分尊重学生的意愿,时间匹配上就有机会进去,进去后要是没别人适合部门,也可能被淘汰,不支持淘汰后包分配,感觉合情合理。问了下其他同学样例的输出结果,大概都有100以上,样例output_data.txt自己也是180个,试了下自己生成的数据,大概是120多个。如此看来,结果应该是不错的,大概是样例数据不大友好吧。

4.总结与心得体会

实际上感觉这次作业的工作量一个人也可以完成,比起先前的Sudoku可爱多了,原来以为要自己写的json的生成和解析也有现成的库可以直接使用,大概主要目的在让我们学会结对编程吧。老实说这次结对编程和队友没有配合的很好,又赶上国庆,最后算是有1+1>1的效果,不过怕是小于2。吸取第一次结对项目交流不够的经验,这次每天都有固定时间开个小会,主要是线上交流,但比起第一次还是有进步的。主要讨论对作业的想法,过于泛化,少有实际性的进展。作业任务的细化工作没有做好,常常讨论完还觉得无从下手,下次讨论的时候还是老问题。所以作业的进度依然没有把握好,最后一天打代码打得飞起,提前见到了三四点的福大,改bug改得走火入魔,莫名亢奋,完全不困,真是糟糕。不过,最后打出来还是很激动的,写完博客也该冷静下来,好好休息,下次也要有所进步。