結對編程作業——畢設導師智能匹配
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<Student> myStudent = new ArrayList<Student>(); //當前老師下的學生
生成程序代碼實現
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<Student> createAllStudent(){CreateMember cm = new CreateMember();List<Student> studentList= new ArrayList<Student>();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<Teacher> createAllTeacher(){CreateMember cm = new CreateMember();List<Teacher> teacherList= new ArrayList<Teacher>();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<Teacher> teacherIterator = Global.teacherList.iterator();
2. for循環遍歷學生集合,學生第n志愿為當前導師的Id則列入局部學生集合變量
List<Student> tempStudentList = new ArrayList<Student>();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<Student> myStudentList = new ArrayList<Student>();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++;}// 更新該老師剩余人數為0sectionCurrent = 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的學習,畢竟后面還有團隊的項目,加油!”
黃家俊:“在此次結對編程中,感受最深的應該是團隊交流吧。在整個過程中我和佳愷的交流基本上不存在障礙,我們都能從對方的表達中得到明確的信息,很容易將各自表達的觀點擴展開來討論。其中很關鍵的一點是,在編碼過程中我遇到一個難題,自己一個人想了許久沒找到根源,然后在和佳愷交流過程中,靈光一閃,找到了解決的方法。所以在這次合作中,愈發感覺到團隊合作的重要性,希望之后還有合作的機會。”