Java初步学习记录(pta作业集总结08-11)
Java初步学习记录
一、前言
以下开始针对Java pta课程成绩系列作业集做出以下总结( ̄▽ ̄):
此次主要分析课程成绩统计系列的题目,事实上相较于先前的菜单是变的简单一点了,而且有了菜单计价程序系列题目的经验,整体体验还好。不过依旧需要花费不少的时间。这种类型的题目要求通过题目所给需求来进行设计,不过由于给出了粗略的类的关系,故而设计起来不算太麻烦。而在第一次基础上的课程成绩统计02添加了实验课的内容,其实在第一次程序代码较好的情况下,只需要略微修改即可。而03则修改较多,而且需要处理较多的报错处。同上次一样,需要整体的设计比较合理才能较为轻松的解决这类题目。(实际上思路清晰设计起来会好很多╮( ̄▽ ̄)╭)
二、设计与分析
1、题目集08
唯有一道题目课程成绩统计,其中最难的点个人认为是如何通过所给的类图设计好类,并符合JAVA中面向对象的设计原则。
7-1 课程成绩统计程序-1
作者 蔡轲 单位 南昌航空大学
题目描述:某高校课程从性质上分为:必修课、选修课,从考核方式上分为:考试、考察。
考试的总成绩由平时成绩、期末成绩分别乘以权重值得出,比如平时成绩权重0.3,期末成绩权重0.7,总成绩=平时成绩*0.3+期末成绩*0.7。
考察的总成绩直接等于期末成绩
必修课的考核方式必须为考试,选修课可以选择考试、考察任一考核方式。
1、输入:
包括课程、课程成绩两类信息。
课程信息包括:课程名称、课程性质、考核方式(可选,如果性质是必修课,考核方式可以没有)三个数据项。
课程信息格式:课程名称+英文空格+课程性质+英文空格+考核方式
课程性质输入项:必修、选修
考核方式输入选项:考试、考察
课程成绩信息包括:学号、姓名、课程名称、平时成绩(可选)、期末成绩
课程信息格式:学号+英文空格+姓名+英文空格+课程名称+英文空格+平时成绩+英文空格+期末成绩
以上信息的相关约束:
1)平时成绩和期末成绩的权重默认为0.3、0.7
2)成绩是整数,不包含小数部分,成绩的取值范围是【0,100】
3)学号由8位数字组成
4)姓名不超过10个字符
5)课程名称不超过10个字符
6)不特别输入班级信息,班级号是学号的前6位。
2、输出:
输出包含三个部分,包括学生所有课程总成绩的平均分、单门课程成绩平均分、单门课程总成绩平均分、班级所有课程总成绩平均分。
为避免误差,平均分的计算方法为累加所有符合条件的单个成绩,最后除以总数。
1)学生课程总成绩平均分按学号由低到高排序输出
格式:学号+英文空格+姓名+英文空格+总成绩平均分
如果某个学生没有任何成绩信息,输出:学号+英文空格+姓名+英文空格+"did not take any exams"
2)单门课程成绩平均分分为三个分值:平时成绩平均分(可选)、期末考试平均分、总成绩平均分,按课程名称的字符顺序输出
格式:课程名称+英文空格+平时成绩平均分+英文空格+期末考试平均分+英文空格+总成绩平均分
如果某门课程没有任何成绩信息,输出:课程名称+英文空格+"has no grades yet"
3)班级所有课程总成绩平均分按班级由低到高排序输出
格式:班级号+英文空格+总成绩平均分
如果某个班级没有任何成绩信息,输出:班级名称+英文空格+ "has no grades yet"
异常情况:
1)如果解析某个成绩信息时,课程名称不在已输入的课程列表中,输出:学号+英文空格+姓名+英文空格+":"+课程名称+英文空格+"does not exist"
2)如果解析某个成绩信息时,输入的成绩数量和课程的考核方式不匹配,输出:学号+英文空格+姓名+英文空格+": access mode mismatch"
以上两种情况如果同时出现,按第一种情况输出结果。
3)如果解析某个课程信息时,输入的课程性质和课程的考核方式不匹配,输出:课程名称+" : course type & access mode mismatch"
4)格式错误以及其他信息异常如成绩超出范围等,均按格式错误处理,输出"wrong format"
5)若出现重复的课程/成绩信息,只保留第一个课程信息,忽略后面输入的。
信息约束:
1)成绩平均分只取整数部分,小数部分丢弃
参考类图:

输入样例1:
仅有课程。例如:
java 必修 考试
数据结构 选修 考试
形式与政治 选修 考察
end
输出样例1:
在这里给出相应的输出。例如:
java has no grades yet
数据结构 has no grades yet
形式与政治 has no grades yet
输入样例2:
单门考试课程 单个学生。例如:
java 必修 考试
20201103 张三 java 20 40
end
输出样例2:
在这里给出相应的输出。例如:
20201103 张三 34
java 20 40 34
202011 34
输入样例3:
单门考察课程 单个学生。例如:
java 选修 考察
20201103 张三 java 40
end
输出样例3:
在这里给出相应的输出。例如:
20201103 张三 40
java 40 40
202011 40
输入样例4:
考试课程 单个学生 不匹配的考核方式。例如:
java 必修 考试
20201103 张三 java 20
end
输出样例4:
在这里给出相应的输出。例如:
20201103 张三 : access mode mismatch
20201103 张三 did not take any exams
java has no grades yet
202011 has no grades yet
输入样例5:
单门课程,单个学生,课程类型与考核类型不匹配。例如:
java 必修 考察
20201103 张三 java 40
end
输出样例5:
在这里给出相应的输出。例如:
java : course type & access mode mismatch
java does not exist
20201103 张三 did not take any exams
202011 has no grades yet
输入样例6:
单门课程,多个学生。例如:
java 选修 考察
20201103 李四 java 60
20201104 王五 java 60
20201101 张三 java 40
end
输出样例6:
在这里给出相应的输出。例如:
20201101 张三 40
20201103 李四 60
20201104 王五 60
java 53 53
202011 53
输入样例7:
单门课程,单个学生,课程类型与考核类型不匹配。例如:
形式与政治 必修 考试
数据库 选修 考试
java 选修 考察
数据结构 选修 考察
20201103 李四 数据结构 70
20201103 李四 形式与政治 80 90
20201103 李四 java 60
20201103 李四 数据库 70 78
end
输出样例7:
在这里给出相应的输出。例如:
形式与政治 必修 考试
数据库 选修 考试
java 选修 考察
数据结构 选修 考察
20201103 李四 数据结构 70
20201103 李四 形式与政治 80 90
20201103 李四 java 60
20201103 李四 数据库 70 78
end
输入样例8:
单门课程,单个学生,成绩越界。例如:
数据结构 选修 考察
20201103 李四 数据结构 101
end
输出样例8:
在这里给出相应的输出。例如:
wrong format
数据结构 has no grades yet
输入样例9:
多门课程,多个学生,多个成绩。例如:
形式与政治 必修 考试
数据库 选修 考试
java 选修 考察
数据结构 选修 考察
20201205 李四 数据结构 70
20201103 李四 形式与政治 80 90
20201102 王五 java 60
20201211 张三 数据库 70 78
end
输出样例9:
在这里给出相应的输出。例如:
20201102 王五 60
20201103 李四 87
20201205 李四 70
20201211 张三 75
java 60 60
数据结构 70 70
数据库 70 78 75
形式与政治 80 90 87
202011 73
202012 72
代码长度限制 16 KB
时间限制 1000 ms
内存限制 64 MB
Steps:第一步是类的设计,题目中只简要给出了类之间的联系,但明显其中除了选课外,其他的学生,课程,成绩均单独存在,即之间没有组合关系。这样有一个很明显的好处便是降低耦合性,这样在修改其中任意一个类的话,其他类无需修改,这也是后续02、03升级能较为容易处理的原因。而后再去考虑输入与输出的匹配问题。具体类图如下:

Ideas:顺着题目所给出的类一步步分析并设计完善,最后再完成主方法的设计。先分析Student类的设计,根据题目描述,其中有两个String类型的变量分别为学号于姓名,并完善getter和setter方法。由于题目要求按照学生学号排序输出,故而需要让Student类实现Comparable接口。
class Student implements Comparable<Student>{
private String number;//学生学号
private String name;//学生姓名
public Student() {
}
public Student(String number, String name) {
this.number = number;
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int compareTo(Student o) {
if (this.getNumber().compareTo(o.getNumber()) > 0){
return 1;
}else if (this.getNumber().compareTo(o.getNumber()) == 0){
return 0;
}
return -1;
}
}
其次Class类的设计,由类图可知,Class和Student为组合关系,故而Class中必然含有多个学生,可以通过ArrayList存储。此外Class类中还有个String类型的班级号。由于Class成绩的输出同样需要排序,故也需要实现Comparable接口。
class Class implements Comparable<Class>{
private String classNumber;//班级号
private ArrayList<Student> students;//整个班级
public Class() {
students = new ArrayList<>();
}
public Class(String classNumber) {
this.classNumber = classNumber;
students = new ArrayList<>();
}
public String getClassNumber() {
return classNumber;
}
public void setClassNumber(String classNumber) {
this.classNumber = classNumber;
}
public ArrayList<Student> getStudents() {
return students;
}
public void setStudents(ArrayList<Student> students) {
this.students = students;
}
public Student findStudent(String studentNumber) {
for (int i = 0; i < students.size(); i++) {
if (students.get(i).getNumber().equals(studentNumber)) {
return students.get(i);
}
}
return null;
}
@Override
public int compareTo(Class o) {
if (this.getClassNumber().compareTo(o.getClassNumber()) > 0){
return 1;
}else if (this.getClassNumber().compareTo(o.getClassNumber()) == 0){
return 0;
}
return -1;
}
}
随后是Course类的设计,相对而言也比较简单,其中包含三个String类型成员变量分别为课程名称、课程性质、考核方式,给出对应的getter以及setter方法即可。相对麻烦一点的是因为课程大部分为中文输入,而题目要求通过首字母排序,所以在上网查询后使用Comparator进行排序可以实现该功能,如下所示。
Comparator<Course> comparator = new Comparator<Course>() {
Collator collator = Collator.getInstance(Locale.CHINA);
@Override
public int compare(Course o1, Course o2) {
return collator.compare(o1.getName(), o2.getName());
}
};
class Course{
private String name;//课程名称
private String quality;//课程性质
private String mode;//考核方式
public Course() {
}
public Course(String name, String quality, String mode) {
this.name = name;
this.quality = quality;
this.mode = mode;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getQuality() {
return quality;
}
public void setQuality(String quality) {
this.quality = quality;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
}
而后是Score类的设计,根据类图可知,Score类同InspectScore以及ExamScore间有着关系,而且其本身为抽象类。所以明显三者为继承关系,其中InspectScore以及ExamScore继承Score类。而Score类中除了构造方法外,其中只需要加上一个返回int类型的抽象方法getFinalScore即可。而其子类ExamScore其中有个int类型的期末成绩,重写Score中的getFinalScore方法。InspectScore则包含两个int类型的成员变量期末成绩以及平时成绩,再重写getFinalScore方法,便可完成这三个类的设计。
abstract class Score {
public Score() {
}
public abstract int getFinalScore();
}
class ExamScore extends Score {
int examScore;
public ExamScore() {
super();
}
public ExamScore(int examScore) {
super();
this.examScore = examScore;
}
public int getExamScore() {
return examScore;
}
public void setExamScore(int examScore) {
this.examScore = examScore;
}
@Override
public int getFinalScore() {
return examScore;
}
}
class InspectScore extends Score {
int inspectScore;
int examScore;
public InspectScore() {
super();
}
public InspectScore(int inspectScore, int examScore) {
super();
this.inspectScore = inspectScore;
this.examScore = examScore;
}
@Override
public int getFinalScore() {
return (int)(inspectScore * 0.3 + examScore * 0.7);
}
}
再者还有ChooseCourse类的设计,其中包含三个变量课程、学生、成绩。按照所需类图设计好组合关系即可。
class ChooseCourse {
Course course;
Student student;
Score score;
public ChooseCourse() {
course = new Course();
student = new Student();
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public Score getScore() {
return score;
}
public void setScore(Score score) {
this.score = score;
}
}
至此,题目中所给出的类的设计以及大功告成,不过同题目所需的输出要求仍然有较大的出入。比如,由于类之间没有组合关系,那么该如何计算某个学生的平均成绩呢,自然需要通过所输入的选课信息找到其每门所选课程的最终成绩,而后再将其总和起来计算得出。所以我们自然就需要多个数组进行存储每次选课信息中出现的学生,课程,班级。所以为了方便起见,我设计了一个School类来进行存储信息以及处理输出的作用。(主要是不想主方法中代码过于冗长)其中的大部分方法都是为了处理后续输出所设计的。
class School {
ArrayList<ChooseCourse> chooseCourses;
ArrayList<Class> allClasses;
ArrayList<Course> allCourses;
ArrayList<Student> allStudents;
public School() {
chooseCourses = new ArrayList<>();
allClasses = new ArrayList<>();
allCourses = new ArrayList<>();
allStudents = new ArrayList<>();
}
public ChooseCourse findChooseCourse(String studentNumber, String courseName) {
for (int i = 0; i < chooseCourses.size(); i++) {
if (chooseCourses.get(i).student.getNumber().equals(studentNumber) && chooseCourses.get(i).course.getName().equals(courseName)) {
return chooseCourses.get(i);
}
}
return null;
}
public Class findClass(String classNumber) {
for (int i = 0; i < allClasses.size(); i++) {
if (allClasses.get(i).getClassNumber().equals(classNumber)){
return allClasses.get(i);
}
}
return null;
}
public Course findCourse(String courseName) {
for (int i = 0; i < allCourses.size(); i++) {
if (allCourses.get(i).getName().equals(courseName)) {
return allCourses.get(i);
}
}
return null;
}
public Student findStudent(String studentNumber) {
for (int i = 0; i < allStudents.size(); i++) {
if (allStudents.get(i).getNumber().equals(studentNumber)) {
return allStudents.get(i);
}
}
return null;
}
public int getStudentScore(String studentNumber) {
int score = 0;
for (int i = 0; i < chooseCourses.size(); i++) {
if (chooseCourses.get(i).student.getNumber().equals(studentNumber)) {
score += chooseCourses.get(i).score.getFinalScore();
}
}
return score;
}
public int getStudentCourse(String studentNumber) {
int course = 0;
for (int i = 0; i < chooseCourses.size(); i++) {
if (chooseCourses.get(i).student.getNumber().equals(studentNumber)) {
course++;
}
}
return course;
}
public int getCourseScore(String courseName,InspectScore inspectScore,ExamScore examScore) {
int finalScore = 0;
for (int i = 0; i < chooseCourses.size(); i++) {
if (chooseCourses.get(i).course.getName().equals(courseName)) {
if (chooseCourses.get(i).score instanceof InspectScore) {
inspectScore.inspectScore += ((InspectScore) chooseCourses.get(i).score).inspectScore;
inspectScore.examScore += ((InspectScore) chooseCourses.get(i).score).examScore;
} else if (chooseCourses.get(i).score instanceof ExamScore) {
examScore.examScore += ((ExamScore) chooseCourses.get(i).score).examScore;
}
finalScore += chooseCourses.get(i).score.getFinalScore();
}
}
return finalScore;
}
public int getCourseStudent(String courseName) {
int student = 0;
for (int i = 0; i < chooseCourses.size(); i++) {
if (chooseCourses.get(i).course.getName().equals(courseName)) {
student++;
}
}
return student;
}
}
随着类的设计完善,后续便是分析题目的输入输出设计主方法了。一般而言,个人比较喜欢先将对应的基本的正则表达式设计好,这样比较方便后续处理各种输入可能的判断。而后再通过if条件的判断对各种输入信息进行处理,再输出结果。
下面便从课程信息输入开始一一开始介绍。明显课程类型信息的输入有四种,课程名字任意,两种课程类型*两种考核方式。故其正则表达式如下所示。
regex[0] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} (必修|选修) (考试|考察)";
而后因为存在课程类型与考核方式不匹配的情况,所以可以再添加两个正则表达式用于细分判断。如下所示
regexCourse[0] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} 必修 考试";
regexCourse[1] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} 选修 (考试|考察)";
随后便对课程信息的处理了,如果其符合正则的判断,则将该信息中的课程加入School中,否则按照题目要求进行输出。
while(scan.matches(regex[0])){
if ((scan.matches(regexCourse[0]) || scan.matches(regexCourse[1]))) {
if (school.findCourse(splitScan[0]) == null) {
Course newCourse = new Course(splitScan[0], splitScan[1], splitScan[2]);
school.allCourses.add(newCourse);
}
}else {
System.out.println(splitScan[0] + " : course type & access mode mismatch");
break;
}
scan = input.nextLine();
splitScan = scan.split(" ");
}
其中scan是String类型的变量存储输入信息,splitScan是String类型的数组用于存储以空格为分隔符分割后的信息。
随后是选课信息的录入。同上所述,优先写好其对应的正则表达式以及细分后的正则表达式。而再进行信息的处理。而后通过其输入样例4可知,哪怕选课信息中课程方式不匹配,依旧会将该学生信息和班级存入School中。而且对于完全重复的信息需要进行过滤,所以School中可以增加find方法,用于查找Course以及Student和Class。
其中Course通过name查找,Student通过number查找,Class通过number查找。
注意最后的Score信息需要通过多态将其加入所创建的ChooseCourse类中。其中的其他细节处理此处便不在赘述。
while (scan.matches(regex[1])){
ChooseCourse chooseCourse = new ChooseCourse();
if (school.findChooseCourse(splitScan[0],splitScan[2]) == null) {
chooseCourse.student = new Student(splitScan[0],splitScan[1]);
String classNumber = splitScan[0].substring(0, 6);
if (school.findStudent(splitScan[0]) == null) {
school.allStudents.add(chooseCourse.student);
}
if (school.findClass(classNumber) == null) {
Class newClass = new Class(classNumber);
newClass.getStudents().add(chooseCourse.student);
school.allClasses.add(newClass);
}else if (school.findClass(classNumber).findStudent(splitScan[0]) == null){
school.findClass(classNumber).getStudents().add(chooseCourse.student);
}
if (school.findCourse(splitScan[2]) != null) {
chooseCourse.course = school.findCourse(splitScan[2]);
if ((scan.matches(regexStudent[0]) && "考察".equals(chooseCourse.course.getMode())) || (scan.matches(regexStudent[1]) && "考试".equals(chooseCourse.course.getMode()))) {
if (scan.matches(regexStudent[0])){
chooseCourse.score = new ExamScore(Integer.parseInt(splitScan[3]));
} else if (scan.matches(regexStudent[1])) {
chooseCourse.score = new InspectScore(Integer.parseInt(splitScan[3]),Integer.parseInt(splitScan[4]));
}
school.chooseCourses.add(chooseCourse);
} else {
System.out.println(splitScan[0] + " " + splitScan[1] + " : access mode mismatch");
}
}else {
System.out.println(splitScan[2] + " does not exist");
}
}
scan = input.nextLine();
splitScan = scan.split(" ");
}
而后为较为麻烦的桌号信息的录入,判断标准为分割出的字符串组有四组且第一组字符串等同字符串"table"。随后直接输出对应桌号的信息(这种写法不推荐,建议再开一个tableNumber数组记录对应的桌号)。之后便是时间信息的处理,去网上查到了一种对于任意格式时间信息输入转化为Date类的方法,而后通过Date类的toString方法转化出的字符串进行处理,其通过split分割出的字符串组中,第二组便是星期的信息简写,所以我们只需要初始化一组装有星期简写的字符串数组weeks,而后通过遍历week找出匹配的字符串下标即可知道此时对应的星期为几。随后将该桌子对应的时间信息分别存入week、hour、min、数组中即可完成桌号信息的存储。
if (splitScan.length == 4 &&"table".equals(splitScan[0])) { //如果格式为桌号识别格式,则录入对应的桌号信息和后续的点菜记录
System.out.println("table" +" " + splitScan[1] + ": ");
tableNumber = Arrays.copyOf(tableNumber,tableNumber.length+1);
tableNumber[tableNumber.length-1] = Integer.parseInt(splitScan[1]);
tablePrice = Arrays.copyOf(tablePrice,tablePrice.length+1);
Date date1 = null;
try {//需要处理异常语句,即格式出错的情况
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH/mm/ss");
date1 = simpleDateFormat.parse(splitScan[2] + " " + splitScan[3]);
} catch (ParseException e) {
e.printStackTrace();
}
String[] splitDate = date1.toString().split(" |:");
for(int i = 0;i < weeks.length;i++){ //获得对应的星期
if(splitDate[0].equals(weeks[i])){
week = Arrays.copyOf(week,week.length+1);
week[week.length-1] = i+1;
break;
}
}
hour = Arrays.copyOf(hour,hour.length+1);
min = Arrays.copyOf(min,min.length+1);
hour[hour.length-1] = Integer.parseInt(splitDate[3]); //获得对应的小时
min[min.length-1] = Integer.parseInt(splitDate[4]); //获得对应的分钟
}
最后是对于存储好的信息将其转化为所需的输出的处理方式。由于需要计算平均分故而肯定需要用循环遍历数据而后再计算。先是Student的平均分输出,如下所示。
for (int i = 0; i < school.allStudents.size(); i++) {
int score = school.getStudentScore(school.allStudents.get(i).getNumber());
int course = school.getStudentCourse(school.allStudents.get(i).getNumber());
if (course != 0){
System.out.println(school.allStudents.get(i).getNumber() + " " + school.allStudents.get(i).getName() + " " + score / course);
}else {
System.out.println(school.allStudents.get(i).getNumber() + " " + school.allStudents.get(i).getName() + " did not take any exams");
}
}
Course平均分的输出。
for (int i = 0; i < school.allCourses.size(); i++) {
InspectScore inspectScore = new InspectScore();
ExamScore examScore = new ExamScore();
int finalScore = 0;
finalScore = school.getCourseScore(school.allCourses.get(i).getName(),inspectScore,examScore);
int students = school.getCourseStudent(school.allCourses.get(i).getName());
if (students != 0) {
System.out.print(school.allCourses.get(i).getName() + " ");
if ("考试".equals(school.allCourses.get(i).getMode())) {
System.out.print(inspectScore.inspectScore / students + " ");
}
System.out.print( (examScore.examScore + inspectScore.examScore) / students + " ");
System.out.println( finalScore / students);
}else {
System.out.println(school.allCourses.get(i).getName() + " has no grades yet");
}
}
注意此处用到的getCourse方法,需要通过强制转换的方式将ChooseCourse中Score的分数加至传入的ExamScore以及InspectScore中。
最后是班级平均分的输出。
for (int i = 0; i < school.allClasses.size(); i++) {
int classScore = 0;
int classCourse = 0;
for (int j = 0; j < school.allClasses.get(i).getStudents().size(); j++) {
classScore += school.getStudentScore(school.allClasses.get(i).getStudents().get(j).getNumber());
classCourse += school.getStudentCourse(school.allClasses.get(i).getStudents().get(j).getNumber());
}
if (classCourse != 0){
System.out.println(school.allClasses.get(i).getClassNumber() + " " + classScore / classCourse);
}else {
System.out.println(school.allClasses.get(i).getClassNumber() + " has no grades yet");
}
}
至此该题目已经基本解决,整体难度还行。开始思路错了,不小心将Student和Course设计成组合关系,即输入选课信息后将对应的Course加入Student中,虽然也能通过大半的测试点,但仍存在不少问题,而且修改起来非常繁琐,随后才重写成不包含组合关系的版本,也算是认识到单一设计原则的重要性了。
2、题目集10
其他几道题为HashMap的简单使用,比较容易就不谈了。主要分析的是7-3 课程成绩统计程序02。
7-3 课程成绩统计程序-2
作者 蔡轲 单位 南昌航空大学
题目描述:课程成绩统计程序-2在第一次的基础上增加了实验课,以下加粗字体显示为本次新增的内容。
某高校课程从性质上分为:必修课、选修课、实验课,从考核方式上分为:考试、考察、实验。
考试的总成绩由平时成绩、期末成绩分别乘以权重值得出,比如平时成绩权重0.3,期末成绩权重0.7,总成绩=平时成绩*0.3+期末成绩*0.7。
考察的总成绩直接等于期末成绩
实验的总成绩等于课程每次实验成绩的平均分
必修课的考核方式必须为考试,选修课可以选择考试、考察任一考核方式。实验课的成绩必须为实验。
1、输入:
包括课程、课程成绩两类信息。
课程信息包括:课程名称、课程性质、考核方式(可选,如果性质是必修课,考核方式可以没有)三个数据项。
课程信息格式:课程名称+英文空格+课程性质+英文空格+考核方式
课程性质输入项:必修、选修、实验
考核方式输入选项:考试、考察、实验
考试/考查课程成绩信息包括:学号、姓名、课程名称、平时成绩(可选)、期末成绩
考试/考查课程信息格式:学号+英文空格+姓名+英文空格+课程名称+英文空格+平时成绩+英文空格+期末成绩
实验课程成绩信息包括:学号、姓名、课程名称、实验次数、每次成绩
实验次数至少4次,不超过9次
实验课程信息格式:学号+英文空格+姓名+英文空格+课程名称+英文空格+实验次数+英文空格+第一次实验成绩+...+英文空格+最后一次实验成绩
以上信息的相关约束:
1)平时成绩和期末成绩的权重默认为0.3、0.7
2)成绩是整数,不包含小数部分,成绩的取值范围是【0,100】
3)学号由8位数字组成
4)姓名不超过10个字符
5)课程名称不超过10个字符
6)不特别输入班级信息,班级号是学号的前6位。
2、输出:
输出包含三个部分,包括学生所有课程总成绩的平均分、单门课程成绩平均分、单门课程总成绩平均分、班级所有课程总成绩平均分。
为避免误差,平均分的计算方法为累加所有符合条件的单个成绩,最后除以总数。
1)学生课程总成绩平均分按学号由低到高排序输出
格式:学号+英文空格+姓名+英文空格+总成绩平均分
如果某个学生没有任何成绩信息,输出:学号+英文空格+姓名+英文空格+"did not take any exams"
2)单门课程成绩平均分分为三个分值:平时成绩平均分(可选)、期末考试平均分、总成绩平均分,按课程名称的字符顺序输出
考试/考察课程成绩格式:课程名称+英文空格+平时成绩平均分+英文空格+期末考试平均分+英文空格+总成绩平均分
实验课成绩格式:课程名称+英文空格+总成绩平均分
如果某门课程没有任何成绩信息,输出:课程名称+英文空格+"has no grades yet"
3)班级所有课程总成绩平均分按班级由低到高排序输出
格式:班级号+英文空格+总成绩平均分
如果某个班级没有任何成绩信息,输出:班级名称+英文空格+ "has no grades yet"
异常情况:
1)如果解析某个成绩信息时,课程名称不在已输入的课程列表中,输出:学号+英文空格+姓名+英文空格+":"+课程名称+英文空格+"does not exist"
2)如果解析某个成绩信息时,输入的成绩数量和课程的考核方式不匹配,输出:学号+英文空格+姓名+英文空格+": access mode mismatch"
以上两种情况如果同时出现,按第一种情况输出结果。
3)如果解析某个课程信息时,输入的课程性质和课程的考核方式不匹配,输出:课程名称+" : course type & access mode mismatch"
4)格式错误以及其他信息异常如成绩超出范围等,均按格式错误处理,输出"wrong format"
5)若出现重复的课程/成绩信息,只保留第一个课程信息,忽略后面输入的。
信息约束:
1)成绩平均分只取整数部分,小数部分丢弃
参考类图(与第一次相同,其余内容自行补充):

输入样例1:
在这里给出一组输入。例如:
java 实验 实验
20201103 张三 java 4 70 80 90
end
输出样例1:
在这里给出相应的输出。例如:
20201103 张三 : access mode mismatch
20201103 张三 did not take any exams
java has no grades yet
202011 has no grades yet
输入样例2:
在这里给出一组输入。例如:
java 实验 实验
20201103 张三 java 3 70 80 90
end
输出样例2:
在这里给出相应的输出。例如:
wrong format
java has no grades yet
输入样例3:
在这里给出一组输入。例如:
java 必修 实验
20201103 张三 java 3 70 80 90 100
end
输出样例3:
在这里给出相应的输出。例如:
java : course type & access mode mismatch
wrong format
输入样例4:
在这里给出一组输入。例如:
java 必修 实验
20201103 张三 java 4 70 80 90 105
end
输出样例4:
在这里给出相应的输出。例如:
java : course type & access mode mismatch
wrong format
代码长度限制 30 KB
时间限制 1000 ms
内存限制 64 MB
Steps:由于是课程成绩统计程序-1的迭代,故而只需着重分析其中黑体标注的部分即可。此处便主讲在添加实验后源代码需要添加的部分。具体类图如下:

Ideas:首先是基本信息输入时需要的正则表达式,代码如下。0为课程信息的录入,1为(必修|选修)课选课信息录入的正则表达式,2为实验课选课信息录入的正则表达式。
regex[0] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} (必修|选修|实验) (考试|考察|实验)";
regex[1] = "\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100)( ([0-9]|[1-9][0-9]|100))?";
regex[2] = "\\d{8} \\S{1,10} \\S{1,10} [4-9]( ([0-9]|[1-9][0-9]|100))*";
随后分析实验课加入后,对应选课信息应如何处理。明显实验课成绩的计算方式既不属于考试成绩也不属于考察成绩。所以可以增添一个实验课成绩类,即ExperimentScore类,其设计方式同ExamScore和InspectScore。如下所示。
class ExperimentScore extends Score {
ArrayList<Integer> experimentScores;
public ExperimentScore() {
super();
experimentScores = new ArrayList<>();
}
public ExperimentScore(ArrayList<Integer> experimentScores) {
super();
this.experimentScores = experimentScores;
}
@Override
public int getFinalScore() {
int allScore = 0;
for (int i = 0; i < experimentScores.size(); i++) {
allScore += experimentScores.get(i);
}
return allScore / experimentScores.size();
}
}
随后主方法中在原先判断课程信息输入匹配后的处理处增加实验方式课程信息的录入即可。其他处基本同第一次程序代码,不在赘述。
while (scan.matches(regex[1]) || scan.matches(regex[2])){
ChooseCourse chooseCourse = new ChooseCourse();
//判断选课信息是否重复
if (school.findChooseCourse(splitScan[0],splitScan[2]) == null) {
chooseCourse.student = new Student(splitScan[0],splitScan[1]);
String classNumber = splitScan[0].substring(0, 6);
//判断学生信息是否重复
if (school.findStudent(splitScan[0]) == null) {
school.allStudents.add(chooseCourse.student);
}
//判断班级信息是否重复
if (school.findClass(classNumber) == null) {
Class newClass = new Class(classNumber);
newClass.getStudents().add(chooseCourse.student);
school.allClasses.add(newClass);
}else if (school.findClass(classNumber).findStudent(splitScan[0]) == null){
school.findClass(classNumber).getStudents().add(chooseCourse.student);
}
//判断课程是否存在
if (school.findCourse(splitScan[2]) != null) {
chooseCourse.course = school.findCourse(splitScan[2]);
//判断课程信息的输入是否匹配
if ((scan.matches(regexStudent[0]) && "考察".equals(chooseCourse.course.getMode())) || (scan.matches(regexStudent[1]) && "考试".equals(chooseCourse.course.getMode())
|| (scan.matches(regexStudent[2]) && "实验".equals(chooseCourse.course.getMode()) && splitScan.length == Integer.parseInt(splitScan[3]) + 4))) {
//如果属于考察方式的课程
if (scan.matches(regexStudent[0])){
chooseCourse.score = new ExamScore(Integer.parseInt(splitScan[3]));
} else if (scan.matches(regexStudent[1])) {
//如果属于考试方式的课程
chooseCourse.score = new InspectScore(Integer.parseInt(splitScan[3]),Integer.parseInt(splitScan[4]));
} else if (scan.matches(regexStudent[2])) {
//如果属于实验方式的课程
ArrayList<Integer> scores = new ArrayList<>();
for (int i = 0; i < splitScan.length - 4; i++) {
scores.add(Integer.parseInt(splitScan[4 + i]));
}
chooseCourse.score = new ExperimentScore(scores);
}
school.chooseCourses.add(chooseCourse);
} else {
System.out.println(splitScan[0] + " " + splitScan[1] + " : access mode mismatch");
}
}else {
System.out.println(splitScan[2] + " does not exist");
}
}
scan = input.nextLine();
splitScan = scan.split(" ");
}
最后在循环计算Course的平均分时,加上Experiment的平均分计算即可。只需要在原先的getCourseScore方法中添加形参ExperimentScore以及加上Experiment均分的计算便完成了。
基本加上实验课程后的变化处均已给出,改的地方不多,唯一比较难一点的就是判断输入的分数个数匹配问题,其实只需要看分组后的数组长度即可╮( ̄▽ ̄)╭。
3、题目集11
同上,其他题目相对较易,不多说。主要看这次在课程成绩统计程序-2基础上迭代的03。需要修改的地方比较多,难度比上次大不少。
7-2 课程成绩统计程序-3
作者 蔡轲 单位 南昌航空大学
题目描述:课程成绩统计程序-3在第二次的基础上修改了计算总成绩的方式,
要求:修改类结构,将成绩类的继承关系改为组合关系,成绩信息由课程成绩类和分项成绩类组成,课程成绩类组合分项成绩类,分项成绩类由成绩分值和权重两个属性构成。
完成课程成绩统计程序-2、3两次程序后,比较继承和组合关系的区别。思考一下哪一种关系运用上更灵活,更能够适应变更。
题目最后的参考类图未做修改,大家根据要求自行调整,以下内容加粗字体显示的内容为本次新增的内容。
某高校课程从性质上分为:必修课、选修课、实验课,从考核方式上分为:考试、考察、实验。
考试的总成绩由平时成绩、期末成绩分别乘以权重值得出,比如平时成绩权重0.3,期末成绩权重0.7,总成绩=平时成绩*0.3+期末成绩*0.7。
考察的总成绩直接等于期末成绩
实验的总成绩等于课程每次实验成绩乘以权重后累加而得。
课程权重值在录入课程信息时输入。(注意:所有分项成绩的权重之和应当等于1)
必修课的考核方式必须为考试,选修课可以选择考试、考察任一考核方式。实验课的成绩必须为实验。
1、输入:
包括课程、课程成绩两类信息。
课程信息包括:课程名称、课程性质、考核方式、分项成绩数量、每个分项成绩的权重。
考试课信息格式:课程名称+英文空格+课程性质+英文空格+考核方式+英文空格+平时成绩的权重+英文空格+期末成绩的权重
考察课信息格式:课程名称+英文空格+课程性质+英文空格+考核方式
实验课程信息格式:课程名称+英文空格+课程性质+英文空格+考核方式+英文空格+分项成绩数量n+英文空格+分项成绩1的权重+英文空格+。。。+英文空格+分项成绩n的权重
实验次数至少4次,不超过9次
课程性质输入项:必修、选修、实验
考核方式输入选项:考试、考察、实验
考试/考查课程成绩信息包括:学号、姓名、课程名称、平时成绩(可选)、期末成绩
考试/考查课程成绩信息格式:学号+英文空格+姓名+英文空格+课程名称+英文空格+平时成绩+英文空格+期末成绩
实验课程成绩信息包括:学号、姓名、课程名称、每次成绩{在系列-2的基础上去掉了(实验次数),实验次数要和实验课程信息中输入的分项成绩数量保持一致}
实验课程信息格式:学号+英文空格+姓名+英文空格+课程名称+英文空格+第一次实验成绩+...+英文空格+最后一次实验成绩
以上信息的相关约束:
1)成绩是整数,不包含小数部分,成绩的取值范围是【0,100】
2)学号由8位数字组成
3)姓名不超过10个字符
4)课程名称不超过10个字符
5)不特别输入班级信息,班级号是学号的前6位。
2、输出:
输出包含三个部分,包括学生所有课程总成绩的平均分、单门课程总成绩平均分、班级所有课程总成绩平均分。
为避免四舍五入误差,
计算单个成绩时,分项成绩乘以权重后要保留小数位,计算总成绩时,累加所有分项成绩的权重分以后,再去掉小数位。
学生总成绩/整个班/课程平均分的计算方法为累加所有符合条件的单个成绩,最后除以总数。
1)学生课程总成绩平均分按学号由低到高排序输出
格式:学号+英文空格+姓名+英文空格+总成绩平均分
如果某个学生没有任何成绩信息,输出:学号+英文空格+姓名+英文空格+"did not take any exams"
2)单门课程成绩按课程名称的字符顺序输出
课程成绩输出格式:课程名称+英文空格+总成绩平均分
如果某门课程没有任何成绩信息,输出:课程名称+英文空格+"has no grades yet"
3)班级所有课程总成绩平均分按班级由低到高排序输出
格式:班级号+英文空格+总成绩平均分
如果某个班级没有任何成绩信息,输出:班级名称+英文空格+ "has no grades yet"
异常情况:
1)如果解析某个成绩信息时,课程名称不在已输入的课程列表中,输出:学号+英文空格+姓名+英文空格+":"+课程名称+英文空格+"does not exist"
2)如果解析某个成绩信息时,输入的成绩数量和课程的考核方式不匹配,输出:学号+英文空格+姓名+英文空格+": access mode mismatch"
以上两种情况如果同时出现,按第一种情况输出结果。
3)如果解析某个课程信息时,输入的课程性质和课程的考核方式不匹配,输出:课程名称+" : course type & access mode mismatch"
4)格式错误以及其他信息异常如成绩超出范围等,均按格式错误处理,输出"wrong format"
5)若出现重复的课程/成绩信息,只保留第一个课程信息,忽略后面输入的。
6)如果解析实验课程信息时,输入的分项成绩数量值和分项成绩权重的个数不匹配,输出:课程名称+" : number of scores does not match"
7)如果解析考试课、实验课时,分项成绩权重值的总和不等于1,输出:课程名称+" : weight value error"
信息约束:
1)成绩平均分只取整数部分,小数部分丢弃
参考类图(与第一次相同,其余内容自行补充):

输入样例1:
在这里给出一组输入。例如:
java 实验 实验 4 0.2 0.3 0.2 0.3
end
输出样例1:
在这里给出相应的输出。例如:
java has no grades yet
输入样例2:
在这里给出一组输入。例如:
java 实验 实验 4 0.2 0.3 0.2
end
输出样例2:
在这里给出相应的输出。例如:
java : number of scores does not match
输入样例3:
在这里给出一组输入。例如:
java 实验 实验 4 0.2 0.3 0.2 0.1
end
输出样例3:
在这里给出相应的输出。例如:
java : weight value error
输入样例4:
在这里给出一组输入。例如:
java 实验 实验 4 0.2 0.3 0.2 0.3
20201116 张三 java 70 80 90 100
end
end
输出样例4:
在这里给出相应的输出。例如:
20201116 张三 86
java 86
202011 86
输入样例5:
在这里给出一组输入。例如:
java 实验 实验 4 0.2 0.3 0.2 0.3
20201116 张三 java 70 80 90 100 80
end
输出样例5:
在这里给出相应的输出。例如:
20201116 张三 : access mode mismatch
20201116 张三 did not take any exams
java has no grades yet
202011 has no grades yet
代码长度限制 50 KB
时间限制 1000 ms
内存限制 64 MB
Steps:主要的新添内容为成绩类的修改以及如何利用新的成绩类计算对应的平均分。具体类图如下:

Ideas:首先是6种基本信息输入时需要的正则表达式,代码如下。当然,根据题目要求,这些都是基础判断的正则表达式。
regex[0] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} (必修|选修|实验) (考试|考察|实验)(( \\d+\\.\\d+){2}| [4-9]( \\d+\\.\\d+)*)?";
regex[1] = "\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100)( ([0-9]|[1-9][0-9]|100))?";
regex[2] = "\\d{8} \\S{1,10} \\S{1,10}( ([0-9]|[1-9][0-9]|100))*";
regex[3] = "end";
regexCourse[0] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} (必修|选修) 考试( \\d+\\.\\d+){2}";
regexCourse[1] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} 选修 考察";
regexCourse[2] = "[\\u4e00-\\u9fa5a-zA-Z]{1,10} 实验 实验 [4-9]( \\d+\\.\\d+)*";
regexStudent[0] = "\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100)";
regexStudent[1] = "\\d{8} \\S{1,10} \\S{1,10} ([0-9]|[1-9][0-9]|100) ([0-9]|[1-9][0-9]|100)";
regexStudent[2] = "\\d{8} \\S{1,10} \\S{1,10}( ([0-9]|[1-9][0-9]|100))*";
而后分析题目的几个新添要求。比如一些新的输出。
1.数量不匹配问题只出现在实验课中,故而只用在匹配实验课信息处判断是否匹配其个数即可。
2.成绩权值不等于1需要在处理完课程信息后进行判断。判断对应数组中weight的和是否为1即可。
将原先的Score类删除,而后新增添的两个类如下所示
class CourseScore{
ArrayList<SplitScore> scores; //各项成绩及其权值
public CourseScore() {
scores = new ArrayList<>();
}
public int getFinalScore() { //获取最终成绩
double finalScore = 0;
for (int i = 0; i < scores.size(); i++) {
finalScore += scores.get(i).score * scores.get(i).weight;
}
return (int)finalScore;
}
public int getAllScore() {
int allScore = 0;
for (int i = 0; i < scores.size(); i++) {
allScore += scores.get(i).score;
}
return allScore;
}
}
class SplitScore{
double weight; //权值
int score; //分值
public SplitScore(double weight, int score) {
this.weight = weight;
this.score = score;
}
}
而后便是将原先Score的部分修改为SplitScore即可,把修改类后大部分报错的地方修改好就完成了。
不难看出,只要第一次课程成绩统计程序设计比较完善,那么后续的迭代其实都比较容易便可完成。所以在程序的基础设计框架下一定要设计完善,尽量提高整体代码的可扩展性,这样后续的迭代会方便许多。
三、踩坑心得
题目集11
7-2课程成绩统计程序-3
开始又犯了老毛病在权值和等于1处直接用==号。
public boolean estimateWeight() {
Double allWeight = 0.0;
for (int i = 0; i < weights.size(); i++) {
allWeight += weights.get(i);
}
return Math.abs(allWeight) == 1;
}

而后采用差值小于0.01判断就可以解决了,分数蹭蹭涨φ(-ω-*)。
public boolean estimateWeight() {
Float allWeight = 0.0F;
for (int i = 0; i < weights.size(); i++) {
allWeight += weights.get(i);
}
return Math.abs(allWeight - 1F) < 0.01F;
}
四、改进建议
个人认为可以改进的地方就是将计算分数的方式进行修改,例如课程成绩统计程序-3可以把其中计算分数的方法去掉放在主方法中,这样能更为方便的去处理学生个数的问题,而且不必传入过多的参数。
public int getCourseScore(String courseName,InspectScore inspectScore,ExamScore examScore) {
int finalScore = 0;
for (int i = 0; i < chooseCourses.size(); i++) {
if (chooseCourses.get(i).course.getName().equals(courseName)) {
if (chooseCourses.get(i).score instanceof InspectScore) {
inspectScore.inspectScore += ((InspectScore) chooseCourses.get(i).score).inspectScore;
inspectScore.examScore += ((InspectScore) chooseCourses.get(i).score).examScore;
} else if (chooseCourses.get(i).score instanceof ExamScore) {
examScore.examScore += ((ExamScore) chooseCourses.get(i).score).examScore;
}
finalScore += chooseCourses.get(i).score.getFinalScore();
}
}
return finalScore;
}
改进过后的代码的可读性和可维护性提高了,使代码更加简洁清晰,易于理解和修改。如下所示
for (int i = 0; i < school.allCourses.size(); i++) {
InspectScore inspectScore = new InspectScore();
ExamScore examScore = new ExamScore();
ExperimentScore experimentScore = new ExperimentScore();
int finalScore = 0;
int students = school.getCourseStudent(school.allCourses.get(i).getName());
for (int j = 0; j < school.chooseCourses.size(); j++) {
if (school.chooseCourses.get(j).course.getName().equals(school.allCourses.get(i).getName())) {
if (school.chooseCourses.get(j).score instanceof InspectScore) {
inspectScore.inspectScore += ((InspectScore) school.chooseCourses.get(j).score).inspectScore;
inspectScore.examScore += ((InspectScore) school.chooseCourses.get(j).score).examScore;
} else if (school.chooseCourses.get(j).score instanceof ExamScore) {
examScore.examScore += ((ExamScore) school.chooseCourses.get(j).score).examScore;
} else if (school.chooseCourses.get(j).score instanceof ExperimentScore) {
experimentScore.experimentScores.addAll(((ExperimentScore) school.chooseCourses.get(j).score).experimentScores);
}
finalScore += school.chooseCourses.get(j).score.getFinalScore();
}
}
if (students != 0) {
System.out.print(school.allCourses.get(i).getName() + " ");
if ("考试".equals(school.allCourses.get(i).getMode())) {
System.out.print(inspectScore.inspectScore / students + " ");
}
System.out.print((examScore.examScore + inspectScore.examScore) / students + " ");
System.out.println(finalScore / students);
} else {
System.out.println(school.allCourses.get(i).getName() + " has no grades yet");
}
}
五、总结
此次题目集主要还是训练了个人设计类的能力,对于面向对象设计的三大原则理解更深刻了。1. 单一职责原则:每个类只负责完成一个单一的职责或功能,使得类的设计更加清晰和简洁。比如课程成绩统计程序-1过渡到课程成绩统计程序-2,只需要修改Course类即可。2. 开放封闭原则:软件实体(类、模块、函数等)应该对扩展是开放的,对修改是封闭的。这意味着一旦实体的行为需要更改,应该通过扩展新的代码来实现,而不是修改原有的代码。3. 里氏替换原则:子类必须能够替换其父类并保持原有的行为,这可以实现代码的扩展和复用。比如课程成绩统计程序-1中Score的两个子类ExamScore和InspectScore。熟悉并遵循这些类的设计原则可以使代码更加灵活、可扩展和易于理解,从而提高软件的可靠性和可维护性。
六、课程评价
面对对象程序设计课程的教学理念(OBE):
评价:这种教学理念有助于学生将理论知识转化为实践技能,促进他们的综合素质提升。
教学方法(边讲边练)- 在课堂上布置现场限时作业
评价:通过边讲边练,学生可以更好地掌握和运用相关概念和技巧,培养独立思考和解决问题的能力。
教学组织(线上线下混合式教学)- 线上学习通线下课堂
评价:这种教学组织方式可以充分利用技术手段,提高教学效率和学生参与度。
教学过程(PTA题目集驱动)- 紧凑的pta作业
评价:这样的教学过程可以帮助学生实践和巩固所学的知识,培养编程思维和解决问题的能力。
教学模式(BOPPPS)- 小组作业讲解JavaFX
评价:这种教学模式通过引入桥梁环节、设立目标、预估学习、参与互动、完成项目和总结反思,帮助学生全面理解和应用面向对象编程的知识和技能。
个人认为这种教学方式对于愿意学习课程的学生而言还是能有效提高其编程能力。但对于一些自主学习能力较差的学生就不太友好了╮( ̄▽ ̄)╭,他们这样学容易基础不牢,很多基本常识都不了解,可能最终也就学的马马虎虎的样子。

浙公网安备 33010602011771号