编码实现一个部门与学生的智能匹配的程序
编码实现一个部门与学生的智能匹配的程序
1. 相关信息
1.1 结对成员学号
我:********* ***
大佬:*******10 ***
1.2 项目Github地址
使用Java语言。
https://github.com/Maple27/S-D_Match
2. 作业需求
详细要求点我
编码实现一个部门与学生的智能匹配的程序。
提供输入包括:
* 20个部门:
* 部门编号(唯一确定值),字符;
* 各部门需要学生数的要求的上限,单个,数值,在[10,15]内;
* 各部门的特点标签,多个(两个以上),字符;
* 各部门的常规活动时间段,多个(两个以上),字符。
* 300个学生:
* 学生编号(唯一确定值),字符;
* 学生空闲时间段,多个(两个以上),字符;
* 兴趣标签,多个(两个以上),字符(学生的兴趣标签一定是所有部门特点标签其中的一个)
* 每个学生有不多于5个的部门意愿(助教测试时测试数据中部门意愿可能会出现空缺,非空缺的部分一定是部门编号中的一个,并按照优先级从高到底的顺序排序)。
实现一个智能自动分配算法,根据输入信息,输出部门和学生间的匹配信息(一个学生可以确认多个他所申请的部门,一个部门可以分配少于等于其要求的学生数的学生) 及 未被分配到学生的部门 和 未被部门选中的学生。
3. 程序设计
3.1 总体思路
1.读入当前目录下input_data.txt文件。
2.根据读入的文件解析并初始化数据(学生,部门的信息)。
3.将部门根据tags的数量进行排序,tags少的部门优先进行匹配。
4.统计学生在各个部门的优先级。
5.根据学生志愿和优先级,在部门与学生之间进行匹配。
6.构造json数据,输出到output_data.txt。
3.2 结构设计
输入和输出:使用了Gson。gson是泓立建议使用的,我之前对其没有任何了解,都是泓立带飞+照着模板打。我个人的理解:gson就是一个用来处理json输入输出的工具,用的时候需要建立专门的输入类和输出类。有模板就是好-v-。
数据储存:专门建立Student、Department类,用上万能的List数组。这部分很简单。
3.3 关键代码
该部分取自于Match.java
3.3.1 统计学生在各部门的分数
思路很简单:学生一周中每有1小时free_time与部门event_schedule时间对应,优先级+1,学生每有一个兴趣tag与部门tags对应,优先级+2。
兴趣的统计很简单。时间的统计:首先创建存储相关信息的List结构,输入数据后想办法对信息(字符串)进行分割。分割用的方法很土,分割后用来存储信息的字符串名字也很土,但是无伤大雅。最后经过一连串的if判断,进行加分。
public void countStudentScore(){
for(int i=0;i<departments.size();i++){
for(int j=0;j<students.size();j++){
List<String> dTimes = departments.get(i).getActivityTime();
List<String> sTimes = students.get(j).getFreeTime();
List<String> dTags = departments.get(i).getTags();
List<String> sTags = students.get(j).getInterests();
int score = 0;
//时间分数统计
for(int m=0;m<dTimes.size();m++){
String str1 = dTimes.get(m);
String[] string2 = str1.split("\\.");
String[] string3 = string2[1].split("~");
String[] string4 = string3[0].split(":");
String[] string5 = string3[1].split(":");
String day1 = string2[0];
int start1 = Integer.parseInt(string4[0]);
int end1 = Integer.parseInt(string5[0]);
for(int n=0;n<sTimes.size();n++){
String str2 = sTimes.get(n);
String[] string6 = str2.split("\\.");
String[] string7 = string6[1].split("~");
String[] string8 = string7[0].split(":");
String[] string9 = string7[1].split(":");
String day2 = string6[0];
int start2 = Integer.parseInt(string8[0]);
int end2 = Integer.parseInt(string9[0]);
if(day1.equals(day2)){
if(end1<=start2||end2<start1){
score += 0;
}
else if(start1<start2&&end1>start2&&end1<end2){
score += end1-start2;
}
else if(start2<start1&&end2>start1&&end2<end1){
score += end2-start1;
}
else if(start1>start2&&end1<end2){
score += end1-start1;
}
else if(start2>start1&&end2<end1){
score += end2-start2;
}
}
}
}
//兴趣分数统计
for(int p=0;p<dTags.size();p++){
String tag1 = dTags.get(p);
for(int q=0;q<sTags.size();q++){
String tag2 = sTags.get(q);
if(tag1.equals(tag2)){
score += 2;
}
}
}
//将此部门与学生时间匹配的分数加入学生信息中
students.get(j).getScores().add(score);
}
}
}
3.3.2 部门编号选学生志愿匹配算法
思路:从tags最少的部门开始,先对学生以在该部门的优先级进行排序,再按第一志愿到最低志愿,遍历学生,符合即选中。注释说的很明白了。
public void match_DtoS(){
for(int i=0;i<departments.size();i++){//部门匹配
int num = 0,flag = 0;
//每次对一个部门招募时以学生对此部门的分数对学生进行重新排列
sortStudent(students, 0, students.size()-1, i);
for(int j=0;j<5;j++){//志愿匹配
for(int k=0;k<students.size();k++){//学生匹配
//超限 下一个
if(num>=departments.get(i).getLimit()){
flag=1;
break;
}
//志愿不足 下一个
if(students.get(k).getWillsSize()<=j) continue;
String wills = students.get(k).getWills().get(j);
String no = departments.get(i).getId();
if(wills.equals(no)){
//符合志愿条件,根据符合分数进行分配
departments.get(i).getMembers().add(students.get(k));
students.get(k).setFlag(1);
num++;
}
}
if(flag==1) break;
}
departments.get(i).setNum(num);
}
for(int i=0;i<departments.size();i++){
for(int p=0;p<departments.get(i).getMembers().size()-1;p++){
for(int j=departments.get(i).getMembers().size()-1;j>p;j--){
if(departments.get(i).getMembers().get(j).equals(departments.get(i).getMembers().get(p))){
departments.get(i).getMembers().remove(j);
}
}
}
}
}
4. 数据生成
这里是我们自认为生成“最好的数据”。
数据生成的代码储存于DataMaker.java中。
看了一下题干,大概能总结出数据一共只有这三类:
- 纯数字的。这类直接用限定范围的随机数生成就行了。嗯,学号还要考虑用过的不能再用。
- 单词类的,比如说部门名称、兴趣名称什么的。这种单词的个数都是有限的,直接弄一个表(数组实现),生成的时候直接从里面随机拿取数据。
- 时间。这个就比较麻烦了。我们把字符串各个元素拆开,先随机生成各个部分,最后再拼起来。
想得美,做得难。做到后面我们发现其实细节上的麻烦还真不少。比如说我们要处理诸如时间不能交叉的各种问题,也要慢慢调试时间的拼接格式。
我们考虑到如下情况:学号和部门号不能出现重复,对于同一个学生/部门标签不能重复,学号只能在限定的范围内出现,生成的时间不能交叉重叠,相邻的时间段也要考虑,不会生成奇葩的时间段(比如说凌晨3:00~4:00之类的)。
一些问题我们没有考虑到:
时间只能生成整点,时间不能够跨天。时间生成整点的确是硬伤,有样例数据的诱导因素在,但是做完之后不想改了-v-。时间不能跨天是我们不想做,现实中可能有些部门会进行深夜活动之类的,也会有学生熬夜的,很烦。另外,粗略看来,生成的数据还是无法反映真实世界的情况。暂时就写到这里。
5. 代码规范
缩进格式:Tab。
行宽:一般不做限制,长行应该尽量少。
注释:2行以上的使用/**/
,否则使用//
。
结构:可以使用形如if(cond) do();
的结构。{
应该挂在括号右边。
类方法:尽量简短。
命名:类名使用大驼峰,函数名、变量名等使用小驼峰,名称过长时可以使用下划线做分割。
示例
Gson gson = new GsonBuilder().setPrettyPrinting().create();
OutputBean outputBean = new OutputBean();
for(int i=0;i<students.size();i++){
if(students.get(i).getFlag()==0){
outputBean.getUnlucky_student().add(students.get(i).getId());
}
}
for(int j=0;j<departments.size();j++){
if(departments.get(j).getNum()==0){
outputBean.getUnlucky_department().add(departments.get(j).getId());
}else{
AdmittedBean admittedBean = new AdmittedBean();
for(int k=0;k<departments.get(j).getMembers().size();k++){
admittedBean.getMember().add(departments.get(j).getMembers().get(k).getId());
}
admittedBean.setDepartment_no(departments.get(j).getId());
outputBean.getAdmitted().add(admittedBean);
}
}
6. 结果分析
根据生成的数据,返回的结果达到了预设的要求。但是我们的程序的问题还是显而易见的。
首先我们构造的数据还是与真实的情况有所出入。比如说学生的课余时间一般都在周末,以及时间段的划分还跟课程表和固定活动(比如吃饭)有关,这点我们望尘莫及。从tag最少的部门排序,tag多的部门就很难找到人,这不公平。也很容易出现兴趣广泛的学生报了多个tag单一且一致的社团。优先级也有问题,部门没办法筛选出关键时间不能参加活动的学生。诸如此类。
7. 结对感受
第一次尝试结对编程,真的是被大佬带着飞啊。。本来想用c++做,然后泓立建议使用java,我对java的理解也就仅限于基本语法了,结果就是变成了老师-学生的模式-_-。最后嘛,Java知识的确学到了一些。大佬打代码行云流水,我还看不太懂,我就在接盘的时候问这问那。然后到我打的时候寸步难行,大佬在一旁指点。。不过我们一直觉得实际上不太适应结对这种方式,因为打代码的时候实际上人是不喜欢被突然打断的。最后一天代码文件打包上传至github。