结对编程作业——毕设导师智能匹配
031402317 李佳恺
031402511 黄家俊
问题描述及要求
输入30个老师(包含带学生数的要求的上限,单个数值,在[0,8]内),100个学生(包含绩点信息),每个学生有5个导师志愿(志愿的导师可以重复但不能空缺),要求另外写生成程序随机实现
实现一个智能自动分配算法,根据输入信息,输出导师和学生间的匹配信息(一个学生只能有一个确认导师,一个导师可以带少于等于其要求的学生数的学生) 及未被分配到学生的导师和未被导师选中的学生,要求输出的未被导师选中的学生人数越少越好。
为输入输出设计标准化、通用化、可扩展的接口,为该智能匹配程序模块后期可能的整合入系统提供便利
输入输出的格式,如采用文本文件或数据库的方式输入,可自由讨论确定,但需要明确,为后期可能的整合入系统提供便利
需要为智能匹配算法确立几条分配或排序原则,比如 绩点优先、或其他、或其他等等
编程语言:JAVA
模块设计
实体类(entity)
全局类(global)
方法类(method)
测试类(test)
学生类和导师类的定义
/**学生实体类**/
public class Student {
private String sName; // 学生姓名
private float gradePoint; // 学生绩点
private int[] sapplication = new int[5]; // 学生志愿
private int teacherId; // 已选中老师编号
/*** 教师实体类**/
public class Teacher {
private int tId; //教师编号
private String tName; //教师姓名
private int sectionMax; //区间最大值
private int sectionRest; //剩下人数
private List myStudent = new ArrayList(); //当前老师下的学生
生成程序代码实现
1. 采用JAVA的random()函数在字符串区间中随机选择5个字符生成学生或导师的姓名
//生成学生随机姓名,教师姓名方法一样
public String createStudentName(){
String stringBase = "abcdefghijklmnopqrstuvwxyz";
Random random = new Random();
StringBuffer studentName = new StringBuffer();
for (int i = 0; i < 6; i++) {
int number = random.nextInt(stringBase.length());
studentName.append(stringBase.charAt(number));
}
return studentName.toString();
}
2. random()函数可以产生[0,1]区间的小数,利用其随机产生[1,5]区间的浮点数作为学生绩点
//产生学生的绩点
public float createGradePoint(){
Random random = new Random();
float gradePoint = random.nextFloat()*4+1;
gradePoint = (float) ((int)((gradePoint*100+5))/100.0);//取小数点后两位
return gradePoint;
}
3. 随机产生[1,30]区间的整数作为学生志愿(整数对应导师的Id)
//产生学生的志愿
public int[] creatApplication(){
int[] application = new int[5];
Random random = new Random ();
boolean[] bool = new boolean[30];
int randInt = 0;
/**得到5个不同的随机数*/
for(int i = 0; i < 5 ; i++) {
do{
randInt = random.nextInt(30);
}while(bool[randInt]);
bool[randInt] = true;
application[i]=randInt+1;
}
return application;
}
4. 随机产生[0.8]区间的整数作为老师带学生数的上限
//产生区间上限
public int createSectionMax(){
Random random = new Random();
int sectionMax = random.nextInt(8);
return sectionMax;
}
5. 定义集合来储存100个学生和30个导师(setXXX()方法在学生类和导师类里面定义,用于学生或导师的信息的初始化,另创建class文件定义集合的全局变量)
//产生学生对象
public List createAllStudent(){
CreateMember cm = new CreateMember();
List studentList= new ArrayList();
for(int i=0;i<100;i++){
Student s = new Student();
s.setsName(cm.createStudentName());
s.setGradePoint(cm.createGradePoint());
s.setSapplication(cm.creatApplication());
s.setTeacherId(0);
studentList.add(s);
}
return studentList;
}
//产生教师对象
public List createAllTeacher(){
CreateMember cm = new CreateMember();
List teacherList= new ArrayList();
for(int i=1;i<=30;i++){
Teacher t = new Teacher();
t.settId(i);
t.settName(cm.createTeacherName());
int temp = cm.createSectionMax();
Global.countAll+=temp;
t.setSectionMax(temp);
t.setSectionRest(temp);
teacherList.add(t);
}
return teacherList;
}
匹配算法思想以及代码实现
志愿优先原则,五轮匹配,每轮从选择学生上限未满的导师Id出发,每个导师可以匹配到若干个当前轮次n,学生第n志愿是该老师Id的局部学生集合,并从集合中选择学生
考虑到在上述匹配到的局部学生集合当中学生应当有个排序以实现老师从中筛选出排名靠前的学生,于是这个排序就非常重要
我们知道每个导师的热门程度不同,因此我们定义学生未满的导师有一个热度值heat,并在每轮匹配算法的开始更新heat值,我们先遍历当前轮次全部学生的集合(在代码当中我们是从全部学生的集合当中删掉每轮被选中的学生),得到每个导师共有几个选他的学生sum,然后求出heat=sum/sectionRest(sectionRest为当前轮次老师剩余的选择学生上限)
//设置教师热度值
public class SetTeacherHeat {
public void setHeat(){
//遍历还在参加选择的教师
for(Teacher teacher : Global.teacherList){
//累计当前教师此轮的热度值
float heat = 0;
//遍历还在参加选择的学生
for(Student student : Global.studentList){
//存储学生志愿
int[] tempValue = student.getSapplication();
//当学生志愿与当前教师编号,不重复志愿
for(int i=0;i<5;i++){
if(teacher.gettId()==tempValue[i]){
heat++;
break;
}
}
}
//设置当前教师的热度值
heat=heat/teacher.getSectionRest();
teacher.setHeat(heat);
}
}
}
定义学生有一个综合值composite,每轮匹配算法开始的时候更新其值,其值为该学生绩点gradePoint * g + 该学生剩余志愿导师热度值之和 * v,根据该值进行当前导师的局部学生集合的学生排序。老师选择学生的时候看的是学生的绩点,但是为了让更多的学生能够选到导师,我们认为可以看每个学生选的导师的热度值之和,热度值高代表其竞争压力大,综合绩点和热度值来进行排序。其中g + v = 1 ,其比例我们经过多次测试为g=0.5.v=0.5最为合理。
//获得此轮当前学生临时综合值
public class GetTempComposite {
public float getComposite(Student student,int round){
//获得学生志愿
int[] tempValue = student.getSapplication();
//记录学生此轮综合值
int tempComposite=0;
//变量此轮及之后学生志愿所选教师的热度
for(int i=round;i<=round;i++){
//志愿不重复,找到相符就跳出
for(Teacher teacher : Global.teacherList ){
//当前志愿与教师id相符
if(tempValue[i]==teacher.gettId()){
//累加
tempComposite+=teacher.getHeat();
break;
}
}
}
return tempComposite;
}
}
//更新学生综合值,系数比例g,v;
for(Student student : tempStudentList){
GetTempComposite gtp = new GetTempComposite();
float tempComposite = 0;
tempComposite = gtp.getComposite(student, round);
float composite=0;
float gradePoints=0;
gradePoints = student.getGradePoint();
composite=gradePoints*g+tempComposite*v;
student.setComposite(composite);
}
// 实现学生按综合值从大到小排序
class SortByGradePoint implements Comparator {
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
float temp1=s1.getComposite();
float temp2=s2.getComposite();
if (temp1 < temp2)
return 1;
else if (temp1 == temp2) {
return 0;
}
return -1;
}
}
进行第n轮匹配:
1. 设置教师列表迭代器来遍历导师集合
// 设置教师列表迭代器
Iterator teacherIterator = Global.teacherList.iterator();
2. for循环遍历学生集合,学生第n志愿为当前导师的Id则列入局部学生集合变量
List tempStudentList = new ArrayList();
for (Student student : Global.studentList) {
int[] application = student.getSapplication();
//学生第round个志愿与教师匹配
if (application[round] == teacher.gettId()) {
// System.out.println(student.getsName() + "----此轮志愿选择---"
// + teacher.gettName());
// 将此志愿的学生加到临时列表中
tempStudentList.add(student);
}
}
3. 如果集合内的学生总数大于等于老师的可选择的学生人数上限,则根据集合内学生的排名,选择排名靠前的等于老师选择学生人数上限的学生,并把被选择的学生从全部学生的集合中删除,将导师的可选择学生人数上限置零,并把该导师从导师集合中删除
// 按绩点从大到小排序
Collections.sort(tempStudentList, new SortByGradePoint());
// 获得该教师当前限选人数
int sectionCurrent = teacher.getSectionRest();
/**
* 当前申请列表人数大于等于设置人数
*/
if (tempStudentList.size() >= sectionCurrent) {
List myStudentList = new ArrayList();
for (int i = 0; i < sectionCurrent; i++) {
Student s = tempStudentList.get(i);
myStudentList.add(s);
// 将该学生列表添加到当前教师目录下
teacher.addStudent(s);
// 将该学生从总学生列表中移除
Global.studentList.remove(s);
Global.countStu++;
checked++;
}
// 更新该老师剩余人数为0
sectionCurrent = sectionCurrent - sectionCurrent;
teacher.setSectionRest(sectionCurrent);
// 将当前教师存入已完成分配的集合中
Global.doneTeacherList.add(teacher);
// 将该老师从总教师列表中删除
teacherIterator.remove();
}
4. 如果集合内的学生总数小于老师的可选择的学生人数上限,则把集合内的学生添加到老师已选择学生的集合里面,并把集合内的学生从全部学生的集合中删除,将导师的可选择学生人数上限更新
else {
/**
* 当前申请人数小于限选人数
*/
sectionCurrent = sectionCurrent - tempStudentList.size();
// 更新当前教师剩余人数
teacher.setSectionRest(sectionCurrent);
for (int i = 0; i < tempStudentList.size(); i++) {
Student s = tempStudentList.get(i);
// 将该学生添加到当前教师目录下
teacher.addStudent(s);
// 将该学生从全局学生列表中移除
Global.studentList.remove(s);
Global.countStu++;
checked++;
}
}
共五轮,在匹配算法方法中加入参数round表示第几轮,另外建立class文件编写主函数实现算法的调用
public void allocteStudent(int round) {
这是我们进行g和v数值测量得到的结果
输出和主函数
在PrintUtils.java中定义方法输出总的学生和导师信息以及调用匹配算法后详细地结果
在test.java中调用各种方法实现导师选择以及最终输出未被导师选择的学生人数(输出结果在附件中)
总结
软工第一次写代码很慌很慌,一开始是懵逼的,觉得很难的样子,但其实只要开始认真地思考问题,总能慢慢地解析题目,找到突破点,以及通过揣摩老师的要求慢慢地完善代码。代码完成实现输入输出的那一刻很开心。
一次作业任务下来,我们讨论了3次,花费了5个多小时,第一次讨论围绕了生成程序代码的编写和匹配算法的主要思想,这时候我们还没有导师热度值的想法,第二次是项目的建立以及git的使用,实现在coding.net上面提交代码和文件,第三次是对匹配算法的优化,我们提出了热度值的想法,一次一次的讨论,都是为了能在作业当中获得更高的分数,以及实现对自己的突破,并且博客的编写也经过了两个人多次的整合,努力完善博客的内容。
结对感受
对于第一次结对编程,其中的感受是有好有坏的。好处在于两个人的力量无疑比一个人强大很多,并且不是简单的1+1,比如在生成程序和匹配算法思想的讨论当中,我先提出自己的想法,队友马上就能在我的想法上面衍生出更多的想法,并且能发现对方想法当中的缺点并且共同思考改进的方法,提高了工作的效率和减少代码编写当中犯错的次数。坏处在于代码的分工,两个人编写代码的习惯也不同,这就对代码整合造成困难,这是我们作为一个团队在合作当中需要磨合和学习的,所以在结对过程当中我们也认识到这个困难并克服。
李佳恺:“黄家俊的编写代码的能力比我强很对,Java学的比我深入,比我熟悉,所以在结对当中他完成代码的部分较多,所以工作量占得也比较多,而且这是主动去做的,这一点我很不好意思也很感谢,所以我也尽最大努力地写好随笔,希望能为两个人获得更高的分数,并且我需要加紧Java的学习,毕竟后面还有团队的项目,加油!”
黄家俊:“在此次结对编程中,感受最深的应该是团队交流吧。在整个过程中我和佳恺的交流基本上不存在障碍,我们都能从对方的表达中得到明确的信息,很容易将各自表达的观点扩展开来讨论。其中很关键的一点是,在编码过程中我遇到一个难题,自己一个人想了许久没找到根源,然后在和佳恺交流过程中,灵光一闪,找到了解决的方法。所以在这次合作中,愈发感觉到团队合作的重要性,希望之后还有合作的机会。”