一、前言
总的来说,比起菜单系列,课程成绩统计程序要简单的多,而且老师上课要带着设计了一遍类,写的顺畅很多,同时没有那么多错误判断。也是因为菜单系列的磨练,这几次作业完成的都挺快的。另外这次作业中开始大量运用HashMap,HashSet等集合,不再只是简单的使用对象数组,这些都是不熟悉的东西,需要学习集合的用法,遍历,以及一些常用方法等,所有通过这三次作业提升也很大。
1. 课程成绩统计程序-1:相比菜单系列,这次作业主要的难点在于对最终信息的排序的输出,因为之前没有学习过相关的内容,所有一开始写的有点艰难,之后在网上学习了List的sort方法,以及对sort方法的重写才完成。除了写排序的过程其他的倒没啥难点。
2. 课程成绩统计程序-2:比起上次作业,这次作业增加了一个实验课的成绩统计,整体的代码没有核心的算法改变,只是增加了对于实验课的处理,写完这次作业可以很清晰的感受到类的设计合理带来的便捷。
3 课程成绩统计程序-3:这次作业的改变的相较于前两次有较大的改变,改变了课程信息输入和课程成绩信息格式,同时改变了最终成绩的计算方式,以及类之间的关系,代码改动较大,需要判断的错误也变多,但总体难度并不高。
二、代码分析以及踩坑心得
1. 课程成绩统计程序-1:
**************************************以下为类的分析**********************************************
class Course{ String name; //课程名称 String NatureOfCourse; //课程性质 String AssessmentMethod; //考核方法 int sumGrade; //总成绩 int studentNumber; //学生数 Course(String name,String NatureOfCourse,String AssessmentMethod){ this.name=name; this.NatureOfCourse=NatureOfCourse; this.AssessmentMethod=AssessmentMethod; } public String getGrade(HashSet<CourseInformation> map){ //计算学科平时成绩和期末成绩平均分 Iterator<CourseInformation> iterator= map.iterator(); int u=0,f=0; //u为平时成绩,f为期末成绩 while(iterator.hasNext()){ CourseInformation c=iterator.next(); if(c.course.name.equals(this.name)){ if(c.grade instanceof Grade1){ u+=((Grade1)c.grade).usuallyGrade; f+=((Grade1)c.grade).finalGrade; } if(c.grade instanceof Grade2){ f+=((Grade2)c.grade).finalGrade; } } } if(u!=0) return " "+ (u/studentNumber) +" "+ (f/studentNumber); else return " "+ (f/studentNumber); } }
以上为课程类,其中有String类型属性name,表示课程的名称,String类型属性NatureOfCourse,表示课程的性质,String类型属性AssessmentMethod,表示考核方法,此外还有该课程的总成绩和该课程的学生数属性sumGrade和studentNumber。
getGrade(HashSet<CourseInformation> map)方法用于输出课程的平时成绩和期末成绩的平均分。其中参数map表示所有的课程成绩信息。在方法体中遍历map,利用 if 语句获取课程信息表中的与该课程名称相同的课程信息,然后根据该课程信息的成绩类的类型判断考试方法为考试还是考察,然后将对应的成绩加到总和中,并最终除以课程的人数得到平均分,然后输出,有平时成绩的输出平时成绩和期末成绩,没有平时成绩的只输出期末成绩。
class Student{ int number; //学号 String name; //姓名 int sumGrade; //总成绩 int courseNumber; //课程数 public Student(int number,String name){ this.number=number; this.name=name; } }
以上为学生类,其中有int类型的属性number,表示学生的学号,属性name表示学生的名字,还有学生所有课程的总成绩和学生所上的课程数sumGrade和sunNumber。
class CourseInformation{ Course course; //课程 Student student; //学生 Grade grade; //成绩 public CourseInformation(Course course,Student student,Grade grade){ this.course=course; this.student=student; this.grade=grade; } public void setStudentGrade(){ //添加单科成绩至学生总成绩中 student.sumGrade+=grade.getGrade(); student.courseNumber++; } public void setCourseGrade(){ //获得单科的总成绩 course.sumGrade+= grade.getGrade(); course.studentNumber++; } }
以上为课程成绩信息类,其中含有课程类属性course,学生类属性student,成绩类grade。
方法setStudentGrade()用于将该课程成绩信息中的成绩传入学生对象中的成绩总和属性,并将该学生对象中的课程数加1。
方法setCourseGrade()用于将该课程成绩信息中的成绩传入课程对象中的成绩总和属性,并将该课程对象中的学生数加1。
class Class{ int number; //班级号 public Class(int number){ this.number=number; } HashMap<Integer,Student> map=new HashMap<>(); public int getClassGrade(){ //获得班级平均分 Collection<Student> values = map.values(); double averageGrade=0; for(Student i : values){ if(i.courseNumber!=0) averageGrade+= i.sumGrade*1.0/i.courseNumber; } return (int)averageGrade/ map.size(); } public void addStudent(Student student){ //添加学生 map.put(student.number,student); } }
以上为班级类,其中 int 类型的属性number,表示该班级的班级号,还有学生类的HashMap表,表示学生集合。
getClassGrade()方法用于计算班级的平均分,方法体中首先将HashMap表转化为Collection类型表,然后用增强for语句遍历Collection表,在利用 if 语句检测学生对象的课程数是否为0,若不为0,则将该学生的成绩加到总和中,最后将所有学生的成绩总和除以该表的长度得到平均成绩。
addStudent(Student student)方法用于在班级类中的HashMap表中添加元素。
abstract class Grade{ public abstract int getGrade(); } //计算最终成绩考虑平时成绩 class Grade1 extends Grade{ int usuallyGrade; int finalGrade; public Grade1(int usuallyGrade,int finalGrade){ this.usuallyGrade=usuallyGrade; this.finalGrade=finalGrade; } public int getGrade(){ return (int)(usuallyGrade*0.3+finalGrade*0.7); } } //计算最终成绩不考虑平时成绩 class Grade2 extends Grade{ int finalGrade; public Grade2(int finalGrade){ this.finalGrade=finalGrade; } public int getGrade(){ return finalGrade; } }
以上代码为成绩类,首先定义一个抽象成绩类作为父类,其中有一个抽象方法方法getGrade(),用于计算最终成绩以及返回。
Grade1继承抽象类Grade,属于考试成绩类,其中有属性usuallyGrade用于记录平时成绩,属性finalGrade用于记录期末成绩,在方法体中重写getGrade()方法,平时成绩*0.3加上期末成绩*0.7得到最终成绩然后去除小数返回。
Grade2继承抽象类Grade,属于考察成绩类,其中有属性finallGrade用于记录期末考试成绩,在方法体中重写getGrade()方法,直接返回期末成绩。
class CheckInput { String in; String[] str; public CheckInput(String in,String[] str){ this.in=in; this.str=str; } public boolean checkCourse(){ //判断课程信息格式是否输入正确 if(in.matches("(\\S{1,10} 选修 (考试|考察))|(\\S{1,10} 必修)|(\\S{1,10} 必修 考试)")){ return true; } else if(in.matches("\\S{1,10} 必修 考察")){ System.out.println(str[0]+" : course type & access mode mismatch"); return false; } else{ System.out.println("wrong format"); return false; } } public boolean checkGrade(){ //判断成绩信息格式是否输入正确 if(in.matches("(\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100) ([0-9]|[1-9][0-9]|100))|(\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100))")){ return true; } else{ System.out.println("wrong format"); return false; } } public boolean isRepeatGrade(HashSet<CourseInformation> courseInformation){ //判断课程成绩信息是否重复 for(CourseInformation c : courseInformation){ if(c.course.name.equals(str[2])&&c.student.number==Integer.parseInt(str[0])) return false; } return true; } public boolean isCourseExit(HashMap<String,Course> map){ if(map.containsKey(str[2])) { //判断课程中是否存在此课程 return true; } else{ System.out.println(str[2]+" does not exist"); return false; } } public boolean checkAssessmentMethod(HashMap<String ,Course> map,Grade grade){ if(map.get(str[2]).AssessmentMethod.equals("考察") && str.length == 5||map.get(str[2]).AssessmentMethod.equals("考试") && str.length == 4){ //判断输入的成绩数量和课程的考核方式是否匹配 System.out.println(str[0]+" "+str[1]+" : access mode mismatch"); if(grade instanceof Grade1){ ((Grade1) grade).usuallyGrade=0; ((Grade1) grade).finalGrade=0; } if(grade instanceof Grade2){ ((Grade2) grade).finalGrade=0; } return false; } return true; } }
以上代码为输入类,其中含有记录一行输入信息的String类型的属性in,将字符串in用空格分隔开得到的字符串数组str。
Boolean类型checkCourse()方法用于判断课程信息的输入是否正确,若字符串in满足正则表达式 "(\\S{1,10} 选修 (考试|考察))|(\\S{1,10} 必修)|(\\S{1,10} 必修 考试)" 则说明格式正确,返回true,若字符串in满足正则表达式 "\\S{1,10} 必修 考察" 说明输入的课程性质和课程的考核方式不匹配,输出对应的语句并返回false,其他的情况说明格式错误,输出对应的语句并返回false。
boolean类型checkGrade()方法用于判断课程成绩信息的输入是否正确,若字符串in满足正则表达式 "(\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100) ([0-9]|[1-9][0-9]|100))|(\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100))"说明输入格式正确,返回true,不满足则输出对应语句并返回false。
boolean类型isRepeatGrade(HashSet<CourseInformation> courseInformation)方法用于课程成绩信息输入是否重复,其中HashSet<CourseInformation>类型参数courseInformation表示已经出现过的课程成绩信息,首先判断输入的成绩数a量和考核方法是否匹配,若不匹配则输出对应的语句,并将该课程成绩信息对象的成绩都变为0,最后返回false,若匹配则返回true。
boolean类型isCourseExit(HashMap<String,Course> map)方法用于判断输入的课程信息是否存在于之前的课程信息哈希表中,其中参数map即为已经存在的课程信息哈希表。利用map的containsKey()方法进项判断,存在即输出对应的语句然后返回false,不存在返回true。
boolean类型checkAssessmentMethod(HashMap<String ,Course> map,Grade grade)方法用于判断输入的成绩数量和课程的考核方式是否匹配,其中HashSet<CourseInformation>类型参数courseInformation表示已经出现过的课程成绩信息,首先判断输入的成绩数a量和考核方法是否匹配,若不匹配则输出对应的语句,并将该课程成绩信息对象的成绩都变为0,最后返回false,若匹配则返回true。
**************************************以下为主方法的分析**********************************************
public static void main(String[] args) { Scanner input=new Scanner(System.in); HashMap<Integer,Student> students = new HashMap<>(); //学生 索引为学号 HashMap<String,Course> courses = new HashMap<>(); //课程 索引为课程名 HashMap<String,Class> classes=new HashMap<>(); //班级 索引为班级号 HashSet<CourseInformation> courseInformation=new HashSet<>(); //课程成绩信息表 Grade grade =null; CourseInformation c; while(true){ String in=input.nextLine(); String[] str=in.split(" "); CheckInput checkInput=new CheckInput(in,str); //退出 if(in.equals("end")) break; //课程信息录入 if(str.length<=3){ if(checkInput.checkCourse()&&!courses.containsKey(str[0])){ //增加课程 if(str.length==3) courses.put(str[0], new Course(str[0],str[1],str[2])); else courses.put(str[0], new Course(str[0],str[1],"考试")); } } //课程成绩录入 if(str.length>3){ if(checkInput.checkGrade()&&checkInput.isRepeatGrade(courseInformation)){ if(!classes.containsKey(str[0].substring(0,6))) //判断班级是否出现 classes.put(str[0].substring(0,6),new Class(Integer.parseInt(str[0].substring(0,6)))); //增加班级 if(!students.containsKey(Integer.parseInt(str[0]))) //判断学生是否出现 students.put(Integer.parseInt(str[0]),new Student(Integer.parseInt(str[0]),str[1])); //增加学生 classes.get(str[0].substring(0,6)).addStudent(students.get(Integer.parseInt(str[0]))); //在班级中添加学生 if(str.length==4) //最终成绩包括期末成绩 grade=new Grade2(Integer.parseInt(str[3])); if(str.length==5) //最终成绩包括平时成绩和期末成绩成绩 grade=new Grade1(Integer.parseInt(str[3]),Integer.parseInt(str[4])); if(checkInput.isCourseExit(courses)){ //判断课程是否出现 c=new CourseInformation(courses.get(str[2]),students.get(Integer.parseInt(str[0])),grade); courseInformation.add(c); //增加课程成绩信息 for (CourseInformation information : courseInformation) { //将单科成绩添加至学生总成绩中和学科总成绩中 c = information; if (c.student.number == Integer.parseInt(str[0])&&c.course.name.equals(str[2])) if (checkInput.checkAssessmentMethod(courses,grade)) { //判断成绩输入是否和考核方式匹配 c.setStudentGrade(); c.setCourseGrade(); } } } } } }
以上代码为主方法的录入课程信息和课程成绩信息部分
首先new一个学生类HashMap表students,键为学生的学号,值为学生对象,用于存储出现的所有学生;new一个课程HashMap表courses,键为课程的名称,值为课程对象,用于存储出现的所有课程;new一个班级HashMap对象classes,键为班级号,值为班级对象,用于存储出现的所有班级;new一个课程成绩信息HashSet表courseInformation,用于存储出现的课程成绩信息。
然后进入while循环,输入字符串 in,然后将 in 用空格隔开得到字符串数组str,然后利用new一个检查输入的对象。
先判断字符串是否为“end”,若为end则直接跳出循环。若字符串数组的长度小于等于3,则说明该条信息为课程信息,然后判断该课程信息的格式是否正确,和已存在课程中是否存在此课程名称,若格式正确且此课程之前没有出现过,则将此课程添加到课程HashMap表中,因为必修课的考核方法一定为考试,输入时可以不输入考核方式,所以要进行一次判断,若必修课没有输入考核方式,则要补上一个考核方法“考试”。若字符串数组的长度大于3,则说明为课程成绩信息,先判断该条信息的格式是否正确以及该条信息是否重复,若该条信息的格式正确且该条信息没有重复,则判断该条信息的学生和班级是否出现过,没有出现过则new班级或学生对象,并添加到班级HashMap表或学生HashMap表中,然后引用班级对象中的addStudent()方法添加学生到班级中,之后就是对成绩进行处理,根据字符串数组的长度进行判断,若长度为4说明为考察课成绩,new一个Grade2类对象,长度为5说明为考试课成绩,new一个Grade1类对象。之后再判断该条信息中的课程名称是否包含中课程HashMap表courses中,若包含在内则new一个CourseInformation对象,并添加到课程成绩信息HashSet表中,遍历课程成绩信息HashSet表courseInformation,判断该信息的成绩输入是否和考核方式匹配,若匹配则调用CourseInformation类中的方法setStudentGrade()和setCourseGrade()将此条信息的成绩加到学生对象和课程对象的总成绩中。
List<Map.Entry<Integer, Student>> studentList = new ArrayList<>(students.entrySet()); studentList.sort(Map.Entry.comparingByKey()); //输出学生信息 for(int i=0;i< students.size();i++){ Student s=studentList.get(i).getValue(); if(studentList.get(i).getValue().courseNumber!=0) //判断学生是否有成绩 System.out.println(s.number+" "+s.name+" "+ (s.sumGrade/s.courseNumber)); else System.out.println(s.number+" "+s.name+" "+"did not take any exams"); } //输出单科信息 ArrayList<String> s=new ArrayList<>(); for(Map.Entry<String,Course> entry : courses.entrySet()){ Course co=entry.getValue(); if(co.studentNumber!=0) //判断该科目下是否有学生成绩 s.add(co.name+co.getGrade(courseInformation)+" "+ (co.sumGrade/co.studentNumber)); else s.add(co.name+" has no grades yet"); } s.sort((o1, o2) -> { //对输出的课程信息进行排序 return Collator.getInstance(Locale.CHINA).compare(o1, o2); }); for (String str : s) System.out.println(str); s.clear(); //输出班级平均成绩 for(Map.Entry<String,Class> entry : classes.entrySet()){ HashMap<Integer,Student> map=entry.getValue().map; int count=0; for(Map.Entry<Integer,Student> e : map.entrySet()){ count+=e.getValue().courseNumber; } if(count==0) s.add(entry.getValue().number+" has no grades yet"); else s.add(entry.getValue().number+" "+entry.getValue().getClassGrade()); } s.sort((o1, o2) -> { //对输出的课程信息进行排序 return Collator.getInstance().compare(o1, o2); }); for (String str : s) System.out.println(str);
以上代码为主方法的对最终信息进行排序并输出的部分
首先进行学生信息的排序和输出,先将学生类HashMap表students转化为List,然后利用List类中的sort()方法根据键进行排序,排序完成后,进行输出,先判断该学生的课程数是否为0,若为0则说明该学生没有参加任何考试,输出对应的语句,若不为0,则输出该学生的成绩信息,平均成绩利用学生对象中的总成绩除以课程数算出。
然后进行课程信息的排序和输出,先创建一个用于记录每条课程信息的ArrayList数组s,类型为String。遍历课程类HashMap表courses,判断该课程的学生数是否为0,为0则说明该课程无成绩信息,将对应的语句添加到 s 数组中,不为0则利用课程对象中的总成绩和学生数计算出平均成绩并将对应语句添加到数组 s 中,遍历完成后利用 s 数组中的sort()方法对所有的课程信息进行排序,排序结束后输出。
最后对班级的信息进行排序并输出,先遍历班级类HashMap表classes,然后判断每个班级对象中的学生ArrayList数组是否为空,若为空说明该班级没有成绩,并将对应的输出语句添加到 s 数组中,若不为空,调用Class中的getClassGrade()方法获得班级的平均成绩并将对应的输出语句添加到 s 数组中。遍历完成后利用 s 数组中的sort()方法对所有的班级信息进行排序,排序结束后输出。
**************************************以下为踩坑心得**********************************************
1.错误:对课程成绩信息进行排序出错,因为课程的名,既有英文(比如 java),也有中文(比如数据结构),英文和中文的排序规则不同,导致排序错误
纠错:改变sort()方法,将Collator.getInstance().compare(o1, o2)改为Collator.getInstance(Locale.CHINA).compare(o1, o2)。

2.错误:对班级排序的理解错误,错认为对班级排序的是按班级平均分的高低
纠错:重写sort()方法

2、课程成绩统计程序-2
**************************************以下为类的分析**********************************************
比起上次作业,类的改变并不大,主要就是在成绩类中,增加了一个Grade3代表实验课的成绩,还有就是关于实验课的错误判断
class Grade3 extends Grade{ String[] grade; //实验成绩信息 public Grade3(String[] grade){ this.grade=grade; } @Override public int getGrade() { int sum=0; for(int i=4;i<grade.length;i++){ sum+=Integer.parseInt(grade[i]); } return sum/Integer.parseInt(grade[3]); } }
类中有一个记录了每次实验的分数的String类型属性grade数组,getGrade()方法体中的将所有的实验次数的成绩的成绩加起来然后除以实验次数得到实验的平均成绩,并返回去除小数后的数据。
class CheckInput { String in; String[] str; public CheckInput(String in,String[] str){ this.in=in; this.str=str; } public boolean checkCourse(){ //判断课程信息格式是否输入正确 if(in.matches("(\\S{1,10} 选修 (考试|考察))|(\\S{1,10} 必修)|(\\S{1,10} 必修 考试)|(\\S{1,10} 实验 实验)")){ return true; } else if(in.matches("\\S{1,10} ((选修 实验)|(必修 (考察|实验))|(实验 (考试|考核)))")){ System.out.println(str[0]+" : course type & access mode mismatch"); return false; } else{ System.out.println("wrong format"); return false; } } public boolean checkGrade(){ //判断成绩信息格式是否输入正确 if(in.matches("\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100)( [0-9]|[1-9][0-9]|100)?")){ return true; } else if (in.matches("\\d{8} \\S{1,10} \\S{1,10} [4-9].*")) { for(int i=4;i<str.length;i++) if(!str[i].matches("[0-9]|[1-9][0-9]|100")){ System.out.println("wrong format"); return false; } return true; } else{ System.out.println("wrong format"); return false; } } public boolean isRepeatGrade(HashSet<CourseInformation> courseInformation){ //判断课程成绩信息是否重复 for(CourseInformation c : courseInformation){ if(c.course.name.equals(str[2])&&c.student.number==Integer.parseInt(str[0])) return false; } return true; } public boolean isCourseExit(HashMap<String,Course> map){ if(map.containsKey(str[2])) { //判断课程中是否存在此课程 return true; } else{ System.out.println(str[2]+" does not exist"); return false; } } public boolean checkAssessmentMethod(HashMap<String ,Course> map){ //判断输入的成绩数量和课程的考核方式是否匹配 if(map.get(str[2]).AssessmentMethod.equals("实验") && str.length != Integer.parseInt(str[3])+4|| map.get(str[2]).AssessmentMethod.equals("考察") && str.length == 5|| map.get(str[2]).AssessmentMethod.equals("考试") && str.length == 4){ System.out.println(str[0]+" "+str[1]+" : access mode mismatch"); return false; } return true; } }
以上为检查课程成绩输入是否是否正确,主要就是增加了与实验课相关的判断,算法没有改变。
其他的类与上次作业并没有改动,这里就不重复写。
**************************************以下为主方法的分析**********************************************
与上次作业相比,主方法在排序并输出过程中并没有改动,主要的改动在成绩的录入方面。
public static void main(String[] args) { Scanner input=new Scanner(System.in); HashMap<Integer,Student> students = new HashMap<>(); //学生 索引为学号 HashMap<String,Course> courses = new HashMap<>(); //课程 索引为课程名 HashMap<String,Class> classes=new HashMap<>(); //班级 索引为班级号 HashSet<CourseInformation> courseInformation=new HashSet<>(); //课程成绩信息表 Grade grade =null; CourseInformation c; while(true){ String in=input.nextLine(); String[] str=in.split(" "); CheckInput checkInput=new CheckInput(in,str); //退出 if(in.equals("end")) break; //课程信息录入 if(str.length<=3){ if(checkInput.checkCourse()&&!courses.containsKey(str[0])){ //增加课程 if(str.length==3) courses.put(str[0], new Course(str[0],str[1],str[2])); else courses.put(str[0], new Course(str[0],str[1],"考试")); } } //课程成绩录入 if(str.length>3){ if(checkInput.checkGrade()&&checkInput.isRepeatGrade(courseInformation)){ if(!classes.containsKey(str[0].substring(0,6))) //判断班级是否出现 classes.put(str[0].substring(0,6),new Class(Integer.parseInt(str[0].substring(0,6)))); //增加班级 if(!students.containsKey(Integer.parseInt(str[0]))) //判断学生是否出现 students.put(Integer.parseInt(str[0]),new Student(Integer.parseInt(str[0]),str[1])); //增加学生 classes.get(str[0].substring(0,6)).addStudent(students.get(Integer.parseInt(str[0]))); //在班级中添加学生 if(checkInput.isCourseExit(courses)){ //判断课程是否出现 c=new CourseInformation(courses.get(str[2]),students.get(Integer.parseInt(str[0]))); courseInformation.add(c); //增加课程成绩信息 for (CourseInformation information : courseInformation) { //将单科成绩添加至学生总成绩中和学科总成绩中 if (information.student.number == Integer.parseInt(str[0])&&c.course.name.equals(str[2])) if (checkInput.checkAssessmentMethod(courses)) { //判断成绩输入是否和考核方式匹配 if(courses.get(str[2]).AssessmentMethod.equals("考察")) //最终成绩包括期末成绩 grade=new Grade2(Integer.parseInt(str[3])); if(courses.get(str[2]).AssessmentMethod.equals("考试")) //最终成绩包括平时成绩和期末成绩成绩 grade=new Grade1(Integer.parseInt(str[3]),Integer.parseInt(str[4])); if(courses.get(str[2]).AssessmentMethod.equals("实验")){ //实验成绩 grade=new Grade3(str); } c.grade=grade; c.setStudentGrade(); c.setCourseGrade(); } } } } } }
在课程成绩信息的录入中,先判断了成绩的输入是否与考核方法匹配,若匹配则根据该条课程成绩信息中的的课程对象中的考核方法来创建成绩对象,“考察”new一个Grade2对象,"考试"new一个Grade1对象,“实验”new一个Grade3对象,然后刚new的成绩对象赋给课程信息对象中的成绩类属性,最后调用CourseInformation类中的方法setStudentGrade()和setCourseGrade()将此条信息的成绩加到学生对象和课程对象的总成绩中。
**************************************以下为踩坑心得**********************************************
1.错误:班级中没有参加考试的人在计算班级的平均分是有计入在内,导致班级平均分计算错误。
纠错:在计算班级成绩总和时,若学生无成绩,则不计入在班级总人数中。

2、课程成绩统计程序-2
**************************************以下为类的分析**********************************************
class Course{ String name; //课程名称 String NatureOfCourse; //课程性质 String AssessmentMethod; //考核方法 ArrayList<Float> percentage; //成绩权重 int sumGrade; //总成绩 int studentNumber; //学生数 Course(String name,String NatureOfCourse,String AssessmentMethod,ArrayList<Float> percentage){ this.name=name; this.NatureOfCourse=NatureOfCourse; this.AssessmentMethod=AssessmentMethod; this.percentage=percentage; } }
以上为课程类,因为这次作业在课程信息输入中,考试课和实验课都增加了成绩权重,所有在课程类中增加了这个属性percentage,为ArratList类型,其他属性的与前面两次作业一样。此外,因为最终课程信息输出不需要输出平时成绩和期末成绩的平均成绩,所有删除了getGrade(HashSet<CourseInformation> map)方法。
class Grade { ArrayList<Float> percentage; //权重 ArrayList<Integer> score; //分数 Grade(ArrayList<Float> percentage,ArrayList<Integer> score){ this.percentage=percentage; this.score=score; } public int getFinalGrade(){ Iterator<Float> p = percentage.iterator(); Iterator<Integer> s = score.iterator(); float count=0f; while(p.hasNext()){ count+=p.next()*s.next(); } return (int)count; } }
以上为成绩类,按题目要求,更改了继承关系,其中的属性:表示每次成绩权重的ArrayList类型数组percentage,表示每次成绩的分数的ArrayList类型数组score。
getFinalGrade()方法用于获得最终成绩,利用迭代器同时遍历percentage数组和score数组,每次将percentage数组中的元素乘以score数组对应的元素,然后加到总和 sum 中,最后返回去掉小数了的sum。
class CheckInput { String in; String[] str; public CheckInput(String in,String[] str){ this.in=in; this.str=str; } public boolean checkPercentage(ArrayList<Float> p){ //判断分项成绩数量值和分项成绩权重的个数是否相同,分项成绩权重值的总和是否为1 if(str[2].equals("实验")&&p.size()!=Integer.parseInt(str[3])){ System.out.println(str[0]+" : number of scores does not match"); return false; } float sum=0; for (Float a : p) sum+=a; if(0.95>sum||sum>1.05){ System.out.println(str[0]+" : weight value error"); return false; } return true; } public boolean checkCourse(){ //判断课程信息格式是否输入正确 if(in.matches("(\\S{1,10} 选修 (考试|考察).*)|(\\S{1,10} 必修 考试.*)|(\\S{1,10} 实验 实验 [4-9].*)")){ return true; } else if(in.matches("\\S{1,10} ((选修 实验).*|(必修 (考察|实验)).*|(实验 (考试|考察)).*)")){ System.out.println(str[0]+" : course type & access mode mismatch"); return false; } else{ System.out.println("wrong format"); return false; } } public boolean checkGrade(){ //判断成绩信息格式是否输入正确 if(in.matches("\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100)( [0-9]|[1-9][0-9]|100)?")){ return true; } else if (in.matches("\\d{8} \\S{1,10} \\S{1,10}.*")) { for(int i=3;i<str.length;i++) if(!str[i].matches("[0-9]|[1-9][0-9]|100")){ System.out.println("wrong format"); return false; } return true; } else{ System.out.println("wrong format"); return false; } } public boolean isRepeatGrade(HashSet<CourseInformation> courseInformation){ //判断课程成绩信息是否重复 for(CourseInformation c : courseInformation){ if(c.course.name.equals(str[2])&&c.student.number==Integer.parseInt(str[0])) return false; } return true; } public boolean checkAssessmentMethod(HashMap<String ,Course> map){ //判断输入的成绩数量和课程的考核方式是否匹配 if(map.get(str[2]).AssessmentMethod.equals("实验") && str.length !=map.get(str[2]).percentage.size() +3|| map.get(str[2]).AssessmentMethod.equals("考察") && str.length != 4|| map.get(str[2]).AssessmentMethod.equals("考试") && str.length != 5){ System.out.println(str[0]+" "+str[1]+" : access mode mismatch"); return false; } return true; } }
以上代码为检查输入类,做了一些改动
boolean类型checkPercentage(ArrayList<Float> p)方法用于判断分项成绩数量值和分项成绩权重的个数是否相同,分项成绩权重值的总和是否为1,其中参数 p 表示权重组合,首先判断该条信息是否为实验课程,若为实验课程则需判断实验次数是否等于p数组的长度,若不相等则输出相应的语句并返回false,若相等,则将所有权重加起来判断是否在0.95-1.05之间,若不在说明权重加起来不为1,输出对应语句并返回false。
其他的类的改动较小,主要是根据这次题目的输出改变而进行了一些必要的改动,没有核心变化,不做概述。
**************************************以下为主方法的分析**********************************************
本次作业的主方法,只对成绩的录入进行的修改,对输出只有微小的改变
public static void main(String[] args) { Scanner input=new Scanner(System.in); HashMap<Integer,Student> students = new HashMap<>(); //学生 索引为学号 HashMap<String,Course> courses = new HashMap<>(); //课程 索引为课程名 HashMap<String,Class> classes=new HashMap<>(); //班级 索引为班级号 HashSet<CourseInformation> courseInformation=new HashSet<>(); //课程成绩信息表 ArrayList<Float> p; //成绩计算权重 ArrayList<Integer> sc; //成绩表 Grade grade =null; CourseInformation c; while(true){ String in=input.nextLine(); String[] str=in.split(" "); CheckInput checkInput=new CheckInput(in,str); //退出 if(in.equals("end")) break; //课程信息录入 if(in.matches("\\D.*")){ p=new ArrayList<>(); if(checkInput.checkCourse()&&!courses.containsKey(str[0])){ if(str[2].equals("考察")) p.add(1.0F); else if(str[2].equals("考试")){ p.add(Float.parseFloat(str[3])); p.add(Float.parseFloat(str[4])); } else for(int i=4;i<str.length;i++) p.add(Float.parseFloat(str[i])); if(checkInput.checkPercentage(p)) //分项成绩数量值和分项成绩权重的个数是否相同,分项成绩权重值的总和是否为1 courses.put(str[0], new Course(str[0],str[1],str[2],p)); //增加课程 } } //课程成绩录入 if(in.matches("\\d.*")){ sc=new ArrayList<>(); if(checkInput.checkGrade()&&checkInput.isRepeatGrade(courseInformation)){ for(int i=3;i< str.length;i++) sc.add(Integer.parseInt(str[i])); if(!classes.containsKey(str[0].substring(0,6))) //判断班级是否出现 classes.put(str[0].substring(0,6),new Class(Integer.parseInt(str[0].substring(0,6)))); //增加班级 if(!students.containsKey(Integer.parseInt(str[0]))) //判断学生是否出现 students.put(Integer.parseInt(str[0]),new Student(Integer.parseInt(str[0]),str[1])); //增加学生 classes.get(str[0].substring(0,6)).addStudent(students.get(Integer.parseInt(str[0]))); //在班级中添加学生 if(courses.containsKey(str[2])){ //判断课程是否出现 c=new CourseInformation(courses.get(str[2]),students.get(Integer.parseInt(str[0]))); courseInformation.add(c); //增加课程成绩信息 for (CourseInformation information : courseInformation) { //将单科成绩添加至学生总成绩中和学科总成绩中 if (information.student.number == Integer.parseInt(str[0])&&c.course.name.equals(str[2])) if (checkInput.checkAssessmentMethod(courses)) { //判断成绩输入是否和考核方式匹配 grade=new Grade(courses.get(str[2]).percentage,sc); c.grade=grade; c.setStudentGrade(); c.setCourseGrade(); } } } else System.out.println(str[2]+" does not exist"); } } }
以上代码为主方法成绩录入部分
若in满足正则表达式 "\\D.*",即该条信息以非数字字符开头,则该条信息为课程信息,首先创造一个ArrayList类型数组p,用于记录权重,然后根据考核方法将权重添加到 p 中,考核方式为考察,往 p 中添加1,考查方式为考试,则将先将第一个成绩权重添加到数组 p 中,再将第二个成绩权重添加到数组 p 中,考核方式为实验,则利用fou循环将每个权重添加到数组 p 中,之后判断分项成绩数量值和分项成绩权重的个数是否相同,分项成绩权重值的总和是否为1,若checkInput.checkPercentage(p)返回true,则将该课程添加到课程HashMap表中。
若in满足正则表达式 "\\d.*", 即该条信息以数字字符开头,则该条信息为课程成绩信息,首先创造一个ArrayList类型数组sc,用于记录成绩,再判断该条信息的格式无误且没有重复后,将课程成绩录入sc数组中,相比于之前作业录入课程成绩信息,创造成绩类Grade不需要因为课程不同而分别new成绩对象,同一new即可。
**************************************以下为踩坑心得**********************************************
1.错误:因为float类型数据相加,会有些许的误差,导致在计算权重时,权重的累加和不会等于准确的1,导致的错误
纠错:将判断累加权重和是否为1改为范围是否在0.95-1.05之间。

三、总结
1.课程成绩统计程序-1:

可以看到本次作业的圈复杂度很高,达到了31,反映了我的代码的拙劣,以及繁杂,有很大的优化空间,在课程成绩信息录入时,使用的if语句过多,使得代码可读性不高,对哈希表的不熟练使用,也使得代码不够简便。
2.课程成绩统计程序-2:

本次作用的全复杂度仍然很高,达到了33,if 语句的过多的使用的问题仍然存在,代码的可读性不高,仍然具有很高的提高空间,也同时反映了我在学习过程中的懒惰,没有对本就冗长,繁杂的代码进行进一步的优化,导致了增加了内容后,代码的可读性进一步降低。
3.课程成绩统计程序-3:

本次作业的圈复杂度一如既往的高,虽然完成的时间很快,但是有点“面向测试点”,在测试点的过程中,并没有很好的完善代码,而是仅仅解决了单一问题,所有在后续帮助同学测测试点的过程中,出现了很多其他问题,所有要吸取教训,写大作业不能不抱着做完为目的,而是要切实的提升自己的能力。
4.个人心得:一个学期的学习结束,经历了从最开始学习面向对象编程的不适,痛苦,以及深夜测测试点的崩溃,到现在基本可以应付最近的大作业,甚至可以帮助同学完成大作业,我可以真切的感受到这一个学期的成长。相比上个学期C语言的每次作业都是独立的,java每次作业都是迭代的,我知道很多同学在其中某一次就没写出来,导致后面的大作业写的十分艰难,所有我很感谢当时的自己没有放弃,每次都以最大的努力去完成每次大作业,直到最后收获到了丰硕的果实,这个果实不仅是指在能力上的进步,也可以是在意志力方面更加坚毅。在今后的学习中,我势必更加努力的面对所有遇到的困难,成为更好的自己。