第二阶段Blog:题目集4-6

  在经过第一、第二阶段的学习后,目前已经对Java有初步了解了。从一开始的对最基本的语法尚不熟悉,到第一阶段结束时逐步掌握Java新带来的一些如继承、多态等特性,和声明式编程内容正则表达式等,再到现在进一步了解更多如接口,以及更多面向对象思想和设计原则如开闭原则等等,对Java的理解的的确确是在不断加深的。比起一开始更多的把这门课当成“java语言程序设计”,现在在简单的完成PTA作业的情况外,也会更多的去考虑我们这门课真正的内容,即“面向对象程序设计”。

 

 一、前言

 

第四次作业

 

第四次作业的题目分别是:

7-1 水文数据校验及处理

7-2 日期问题面向对象设计(聚合一)

7-3 图形继承

运用知识点

  本次PTA作业应该是运用知识点比较杂糅的一次,3道题也分别考察三个知识点

7-1 运用正则表达式进行合法性判断以及进行数据处理

7-2 聚和关系的类的设计

7-3 继承的使用

难度

  应该来说似乎在4、5、6三次作业中,这次作业反而是最难的,在刚刚学习正则、聚合关系以及继承的时候,完成这三道题确实不是那么轻松。且如第一道题要判断合法性的内容确实非常多,用正则判断闰年2月天数合法性也的确比较麻烦,但这题逻辑思考其实没有那么复杂,相比于上道50分的第三次作业最后一题的情况考虑来说也确实相对少一些。第二道题虽然是35分,但第一次用这样分的较为细碎的类设计来完成内容反而琢磨了我更长的时间,不过其实我们前几次作业也做过类似的日期类设计的题目,有前面内容的铺垫适应起来也还是可以接受。第三题主要是第一次使用继承,但好在只是光使用继承也并没有过于复杂。

7-1 ☆☆☆☆

7-2 ☆☆☆

7-3 ☆☆

 

第五次作业

第五次作业的题目分别是:

7-5 日期问题面向对象设计(聚合二)

7-4 统计Java程序中关键词的出现次数

7-2 合并两个有序数组为新的有序数组

7-3 对整型数据排序

运用知识点

  在本次作业中,前面两题与上次作业一样,分别实在应用聚会类设计和正则表达式判断,而后面两题变成了数据结构的顺序表操作和排序算法……

7-5 聚和关系的类的设计

7-4 运用正则表达式进行进行数据处理

7-2 数据结构——顺序表操作

7-3 数据结构——排序算法

难度

  对于这次作业的后面两题来说,其实基本完全是上个学期数据结构的第一次和最后一次实验内容,并且基本和原来实验课上的内容一样,甚至是与之前C语言的代码内容,循环结构与条件结构都基本相同,而且我还保留了原来数据结构的代码,同时完全可以直接把C语言内容放进去……对于第一题,与上一次的第二题除了输出不同外其他完全一致。第二题则是这次作业的一个难点,且最大的难点难在题目本身的有的一个bug上(#流汗黄豆)。最大的难点其实是如果想通过最后一个测试点,那必须要注意两种注释//与/* */以及双引号“ ”的优先清除逻辑顺序。

7-5 ☆☆

7-4 ☆☆☆☆

7-2 ☆

7-3 ☆

 

第六次作业

第六次作业的题目分别是:

7-1 正则表达式训练-QQ号校验

7-2 字符串训练-字符排序

7-3 正则表达式训练-验证码校验

7-4 正则表达式训练-学号校验

7-5 图形继承与多态

7-6 实现图形接口及多态性

运用知识点

   本次作业继续和前两次作业一样,围绕正则表达式和继承与多态展开,同时引入了接口,但在做题目的时候尚未学到接口

7-1 正则表达式

7-2 字符串训练

7-3 正则表达式训练

7-4 正则表达式训练

7-5 继承与多态

7-6 接口及多态性

难度

  关于正则的部分,与前面的大作业相比起来反而简单许多。这次在继承的部分,则按要求要正式应用多态的概念完成对内容的灵活应用,需要将不同的图形类存入统一的Shape雷构建的列表中,完成系统化的统一操作。二下一题虽然提到接口,确是这一题内容的简化 

7-1 ☆

7-2 ☆

7-3 ☆

7-4 ☆

7-5 ☆☆☆☆

7-6 ☆☆

 

二、设计与分析

 

题目集4(7-2)、题目集5(7-4)两种日期类聚合设计的优劣比较

 

 7-2 日期问题面向对象设计(聚合一)

类图

 

 

 复杂度分析 

 

 

7-5 日期问题面向对象设计(聚合二)

类图

复杂度分析

 

对二者的分析 

  通过类图可以看到,对于题目集4-2,year类作为month类的部分、month类作为day类的部分、day类作为dateuial类的一部分,这四者之间是层层调用的关系,在具体应用的过程中,如求n天后,题目集4-2需要通过this.getday().getmonth().getyear().getvalue()操作才能获取到年份的数值,对年份的操作需要通过day牵扯到month再牵扯到yuar。而在题目集5-1中,year、month、day三个类是平等被dateuial类调用的,对年份数值的查看只需要this.getyear().getvalue()即可,不需对其他两类造成影响。对于这一点上,题目集5-1也更符合通常人的思维逻辑,编写的时候也更容易思考和理解,同时也不会出现超长的如this.getday().getmonth().getyear().getvalue()逻辑串让人难以整理编写。

  在时间关系中,年月日是以时间这个整体存在的,最基本的如年月日时间合法对错,单独的29日,单独的2月,单独的2021年,并不能直接证明时间的合法性。只有当2021.2.29合起来时,合法性判断才有意义。在题目集5-1中,由于year、month、day没有直接关系,因此每当需要进行相关判断时,year、month、day只是独立的分别给出三个数值,核心的组合与判断要全部交给dateuial类完成。而在题目集4-2,由于三个类组合在一起构成的时间概念,因此很大一部分组合判断已经完成,减轻了dateuial类的负担。

  也正是上面这段内容的原因,如soursemonitor复杂度分析的结果一样,虽然在编写复杂度上题目集4-2大于题目集5-1,在清晰度和逻辑理解上题目集5-1也更高一筹,但是由于5-1在在设计上将太多的任务交给dateuial类处理,导致dateuial类呈现一种“上帝类”的状态,因为题目集4-2是聚合关系,题目集5-1三者之间没有直接关系,因此题目集5-1的耦合度较低,每次调用月和天增加和减少的方法都要额外判断进退位,没有简化问题,使题目集5-1的整体复杂度和峰值复杂度都过高,甚至是远高于题目集4-2。

 

 

 

题目集4(7-3)、题目集6(7-5、7-6)三种渐进式图形继承设计的思路与技术运用(封装、继承、多态、接口等)

 

7-3 继承的使用

类图

 

 

 

复杂度分析

 

 

 

7-5 图形继承与多态

源代码

import java.util.*;

public class Main {
    static ArrayList<Shape>alist = new ArrayList<Shape>();
    
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int cnum = scan.nextInt();
        int rnum = scan.nextInt();
        int tnum = scan.nextInt();
        int num = cnum+rnum+tnum;
        
        if(cnum<0 || rnum<0 || tnum<0) {
            Show.showfalse();
            System.exit(0);
        }   

        
            for(int j=0; j<cnum; j++) {
                Circle a = new Circle();
                double r = scan.nextDouble();
                a.settervalue(r);
                alist.add(a);
            }
            for(int j=0; j<rnum; j++) {
                Rectangle a = new Rectangle();
                double w = scan.nextDouble();
                double l = scan.nextDouble();
                a.settervalue(w, l);
                alist.add(a);
            }
            for(int j=0; j<tnum; j++) {
                Triangle a = new Triangle();
                double s1 = scan.nextDouble();
                double s2 = scan.nextDouble();
                double s3 = scan.nextDouble();
                a.settervalue(s1, s2, s3);
                alist.add(a);
            }
        
        
        for(int i=0; i<num; i++) {
            if(!judge(alist.get(i))) {
                Show.showfalse();
                System.exit(0);
            }
        }
        
        for(int i=0; i<num; i++) {
            jisuan(alist.get(i));
        }
        
        Show.showArea();
        for(int i=0; i<num; i++) {
            alist.get(i).getArea();
        }
        System.out.println();
        
        double an = 0;
        for(int i=0; i<num; i++) {
            an += alist.get(i).area;
        }
        Show.showSum(an);
        
        double test[] = new double[num];
        for(int i=0; i<num; i++) {
            test[i] = alist.get(i).area;
        }
        Arrays.sort(test);
        Show.showSort(test);
        
        Show.showSum(an);
        
        
    }
    
    static boolean judge(Shape o) {
        return o.validate();
    }
    
    static void jisuan(Shape o) {
        o.suanArea();
    }
    
}

class Shape{
    double area;
    
    void getArea() {
        System.out.printf("%.2f ",area);
    }
    
    boolean validate() {
        return false;
    }
    
    void suanArea() {
        
    }
}

class Circle extends Shape{
    double radius;
    
    void settervalue(double radius) {
        this.radius = radius;
    }
    
    @Override
    boolean validate() {
        if(radius>0)
            return true;
        return false;
    }
    
    @Override
    void suanArea() {
        area = Math.PI*radius*radius;
    }
}

class Rectangle extends Shape{
    double width;
    double length;
    
    void settervalue(double width, double length) {
        this.width = width;
        this.length = length;
    }
    
    @Override
    boolean validate() {
        if(width>=0 && length>=0)
            return true;
        return false;
    }
    
    @Override
    void suanArea() {
        area = width*length;
    }
}

class Triangle extends Shape{
    double side1;
    double side2;
    double side3;
    double bian[] = new double[3];
    
    void settervalue(double side1, double side2, double side3) {
        this.side1 = side1;
        this.side2 = side2;
        this.side3 = side3;
    }
    
    @Override
    boolean validate() {
        if(side1>=0 && side2>=0 && side3>=0) {
            bian[0] = side1;
            bian[1] = side2;
            bian[2] = side3;
            Arrays.sort(bian);
            if(bian[0]+bian[1]>bian[2]) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    void suanArea() {
        double p=(side1+side2+side3)/2;
        area = Math.sqrt(p*(p-side1)*(p-side2)*(p-side3));
    }
}


class Show{
    public static void showfalse() {
        System.out.println("Wrong Format");
    }
    
    public static void showArea() {
        System.out.println("Original area:");
    }
    
    public static void showSum(double answer) {
        System.out.printf("Sum of area:%.2f\n",answer);
    }
    
    public static void showSort(double test[]) {
        System.out.println("Sorted area:");
        for(int i=0; i<test.length; i++) {
            System.out.printf("%.2f ",test[i]);
        }
        System.out.println();
    }
}

 

类图

 

 

 

复杂度分析

 

7-6 实现图形接口及多态性

类图

 

 

 

 

复杂度分析

 

 

 

对三种渐进式图形继承设计的思路

  对于题目集4-3,它在设计只用到了封装和继承。其中Rectangle, Circle继承自Shape类,Box继承自Rectangle,Ball继承自Circle。在整体上并没有用到多态的特性,因此对于更像是抽象类的Shape(并没有给出实际的公共的属性和方法)并没有发挥太多实际的作用,Rectangle, Circle几乎没有公共的内容。而Ball借用Circle的半径和面积,Box借用Rectangle的长宽和面积,二者又多了更多自己的独自的体积求法,才的确合理的用到了继承,提高了复用性。
  而在题目集6-5中,因为多态的应用,抽象类的Shape才发挥了核心的作用。其中最关键的是通过Shape父类构建图形类列表,将不同的形状类批量化装在Shape列表中,最后在一次for循环中批量化处理。对于Shape类,其定义的area也同样为Rectangle, Circle,Triangle三者共需的属性,而getArea()、validate()、suanArea(),也将三者独立但共同需要进行的方法抽象在了Shape中,在子类中重写后方便多态的使用。在本道题中,多态的应用极大的提高了代码的复用性。
  题目集6-6其实在题目内容上是6-5的简化,但增加了用接口来求面积的操作,同样实现了多态。虽然同是求面积,但是不同题目的设计体现出了不同技术的特性,我们可以通过封装隐藏具体的实现,用继承提高代码复用性,用多态使代码更灵活。

 

 

对三次题目集中用到的正则表达式技术的分析总结

 

  在本轮作业中涉及到正则表达式的主要有:题目集4-1,题目集5-2,题目集6-1、6-3、6-4。事实上,就正则部分而言,三次作业的正则难度是在不断递减的。

题目集6-1至6-4分析

  在题目集6中,所遇到的正则主要是最简单的判断合法性。用.matches(str)语句返回匹配的true与false即可完成。这轮训练中,只是相当于对基础的正则符号如\d [ ] { } * ? | 等进行了解。

1)其中6-3在校验验证码时提供了一个新的思路和方法,通过判断是否为“非标点符号”来锁定内容为大小写字母和数字

 

String pa = "[\\p{Alnum}]{4}";
return astr.matches(pa);

 

在正则简化判断逻辑的基础上,也用逆向思维简化了正则的编写,较大的简化了判断的工作量。

2)而在6-4中,对学号的判断上,因为学号不同的位有不同的含义,可以将表达相同含义的位分组,如1、2位分为年份组,3、4位分为学院组……同组位会因现实情况出现断层。如班级号只有11~17、61、71~73、81~82。合理的利用“或”语句,对同组内情况分析,可以极大的压缩正则表达式的长度。

String pa = "2020(1[1-7]|61|7[123]|8[12])(0[1-9]|[1-3]\\d|40)";
return astr.matches(pa);

 

 

题目集5-2分析

  在题目集5-2中,其核心放在了对字符串的处理上。包括通过如下语句完成对双引号内容的去除,对/*  */中注释的去除,以及对//  注释的去除。(本道题测试点2对符号去除中存在一个=号的问题,过测试点用特殊方法,暂不再这展示)

 

while(!(input=scan.nextLine().trim()).equals("exit")) {
     a.append(input.replaceAll("\".*\"", " ")+" djk ");
}    
this.b = a.toString();
b=b.replaceAll("/\\*(.*?)\\*/", " ");
b=b.replaceAll("//(.*?)djk", " ");

 

  此外,在字符串应用正则替换处理外,还有字符串的匹配内容,通过正则检索获取关键词,从而可以统计关键词的数量。

for(int i=0;i<53;i++) {
    Pattern pattern = Pattern.compile("\\b"+keyword[i]+"\\b");
    Matcher matcher = pattern.matcher(b);
    while(matcher.find()) {
          anum[i]++;
    }
}

 

题目集4-1分析 

  题目集4-1虽然是我们第四次作业,却是正则处理最复杂的一次。其中包括了大量正则和正则之外的字符串的处理,保活字符串分割,类型转换、合法性判断等。其中最复杂的属于运用正则完成对时间闰年2月29日的判断,以及连在一起的时间的判断。判断时间的内容如下。

boolean checkTime() {
        String regex1="([1-9](\\d{0,3}))/((([1-9]|(1[0-2]))/(?:([1-9]|([1-2][0-8])|19)))|(([13578])|([1][02])(/31))|(([13-9]|1[02])/(29|30)))( [02468]| 1[02468]| 2[02]|):00";
        String regex2="(?:((?:([48]|[2468][048]|[13579][26]))|((?:(([1-9](\\d?))?:(0[48]|[2468][048]|[13579][26])))|(?:([48]|[2468][048]|[13579][26])00))/2/29)(?:(?:( [02468]| 1[02468]| 2[02]|):00)))";
        String regexend=regex1+"|"+regex2;
        return astr[0].matches(regexend);
    }

 

 

题目集5(7-4)中Java集合框架应用的分析总结

 

( 题 外 话 )对与本道题而言,其实最大的一个问题是测试点2对标点符号及括号的处理中,有一个bug,存在一个类似 =true 的内容里,true作为关键词却没有被统计上。因此过那个测试点反而要阻止这个测试点被统计上。

其次,对于最后一个测试点,最重要的是字符串处理顺序,“ ” > /* */ > // 。因为如果先处理//  一旦出现这种结构

/*

//  */

则会导致

/* */ 内容注释出现问题,最后无法完成正确判断。

源代码

import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    public static void main(String[] args) {        
        DealString thestr = new DealString();
        thestr.scandata();
        if(!thestr.checknull())
            Show.showFalse();
        else {
            thestr.totalkey();
            Show.display(thestr.anum, thestr.keyword);
        }

    }

}


class Inputdata{
    String b;
    
    void scandata() {
        Scanner scan=new Scanner(System.in);
        StringBuilder a = new StringBuilder();
        String input;
        while(!(input=scan.nextLine().trim()).equals("exit")) {
            a.append(input.replaceAll("\".*\"", " ")+" djk ");//去掉"//"后和的内容以及双引号里的内容
        } 
        this.b = a.toString();   
        b=b.replaceAll("/\\*(.*?)\\*/", " ");
        b=b.replaceAll("//(.*?)djk", " ");
    }
    
}


class DealString extends Inputdata{
    String[] keyword = {"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while"};
    Integer[] anum = new Integer[53];
    
    DealString() {
        for(int i = 0; i < 53; i++) {
            anum[i] = 0;
        }
    }    
    String nowhy(String s) {
        s=s.replace("["," ");
         s=s.replace("]"," ");
       s=s.replace("-","a");
         s=s.replace("*","a");
         s=s.replace("/","a");
         s=s.replace("+","a");
       s=s.replace(">","a");
        s=s.replace("=","a");
       s=s.replace("!","a");
        s=s.replace(":","a");
       s=s.replace("\\","a");
         s= s.replaceAll("[^a-zA-Z]", " ");
         return s;
    }    
    boolean checknull() {
        if(b.length()==0){
            return false;
        }
        return true;
    }
    void totalkey() {
        b=nowhy(b);
        //System.out.println(b);
        for(int i=0;i<53;i++) {
            Pattern pattern = Pattern.compile("\\b"+keyword[i]+"\\b");
            Matcher matcher = pattern.matcher(b);
            while(matcher.find()) {
                anum[i]++;
            }
        }
    }
}

class Show{
    static void display(Integer anum[], String keyword[]) {
        for(int i=0;i<53;i++) {
            if(anum[i]>0) {
                System.out.printf("%d\t%s\n",anum[i],keyword[i]);
            }
        }
    }
    static void showFalse() {
        System.out.print("Wrong Format");
    }
}

 

类图

复杂度分析

 

分析

  对于本道题而言,我个人其实并没有使用java提供的某些列表操作,如Map<String,Integer>。而是更多的参考了原来在数据结构课程上的方法。先创建一个字符串数组存入所有的关键词,再创建一个53个空间的初始值为0的数组。在需要时按顺序调用53个关键词组成用于匹配的字符串的正则表达式去匹配处理完成的输入的字符串内容。当匹配道一次后,就在对应索引的整形数组++。最后整形数组中检测不为0的内容并将其输出即可。

 

 

 

 

三、踩坑心得

 

1、日期相关操作

在7-2 日期问题面向对象设计(聚合一)中,对于求n天前/n天后,我一开始的做法增加了大量if判断,将日期分为n天后不跨月、n天后跨月不跨年、n天后跨年三种类别。在后续修改过程中,我发现可以使用一个while循环代替。如下代码所示,将天数全部加入day中,当天数数值大于当月最大天数a[m],若m不为12,则将天数减去当月的天数最大值,month++,表示度过整个月,进入下个月的判断,当m==12时,天数减去当月最大之后,m=1,year+1,并判断新year是否为闰年,并以此修改2月最大天数。

y=day.month.year.value;
        m=day.month.value;
        d=day.value+n;
        while(d-a[m]>0){//找到月
            if(m == 12) {
                d=d-a[m];
                m = 1;
                y++;
                if(new Year(y).isLeapYear())
                    a[2]=29;
                else
                    a[2]=28;
            }
            else {
                d=d-a[m];
                m++;
            }
            
        }

 求n天前的逻辑类似,但注意操作加减顺序

        y=day.month.year.value;
        d=day.value-n;
        m=day.month.value;
        while(d<=0){//找到月
            if(m==1){
                y--;
                if(new Year(y).isLeapYear())
                    a[2]=29;
                else
                    a[2]=28;
                m = 12;
                d=d+a[m];    
            }
            else {
                d=d+a[--m];
            }
                 
        }

2、字符串处理的顺序

 在题目集5-2中,对于最后一个测试点,其最大的核心是要注意字符串处理的顺序。“ ” > /* */ > // 。在开始的时候,我先为了方便,在输入的时候就处理掉了//  注释。结构最后以各测试点的内容时钟无法通过。因为如果先处理//  一旦出现这种结构/* //  */则会导致/* */ 内容注释出现问题,最后无法完成正确判断。后面我将//  注释的内容留下来,并做好标记,较后才进行处理。最终才通过了相关测试。

 

 

四、改进建议

 

1、题目集5-2  7-4 统计Java程序中关键词的出现次数

和作业三的最后一题一样,这道题目也是存在一个没有修复的判断bug。在编写代码去除符号的过程中,正常的使用replaceAll(“\\p{P}, " " ”)发现无法通过该测试点,在自己调试的过程中将答案输出来后,又确信标点符号已经正常去除。在后续继续测试的过程中才发现。存在一个类似 =true 的内容,true作为关键词却没有被统计上。因此过那个测试点反而要阻止这个有效关键词被统计上。希望这些题目本身的问题能够及时消除掉。

2、题目集5(7-4)日期问题面向对象设计

每次调用月和天增加和减少的方法都要额外判断进退位。其实有一个好的解决方法是将年月日的增减方法放在DateUtil里,因为天数和月份的增减操作不和月份,年份有关系。此外,在求两个日期相差天数的过程中,我所使用的内容也较为复杂了,其实可以使用更简单的方法处理。

public int getDaysofDates(DateUtil date) {
    int d = 0;
    Day t;
    if (this.compareDates(date)) {
        t = date.getDay();
        date.setDay(this.day);
        this.day = t;
    }
    while(!this.equalTwoDates(date)) {
        date.getDay().dayReduction();
        d++;
    }
    return d;
}

 

 

五、总结

  本轮作业其实主要分为三块内容,分别是正则表达式、继承与多态、面向对象关系类的设计。这些也都对应了java理论课上学习的进度,不过难度倒是如前言所谈的那样刚好相反了。这三次大作业牵涉到了许多面向对象的内容,同时我们也在课上学习了老师给出的类图设计。现在已经确实能掌握部分设计一个程序的知识了。与此同时也真实感到面向对象程序设计给编程带来的便利。相比于在未运用面向对象思想之前设计的代码,现在写出的代码在可读性与可拓展性上都有极大的提升,可以说受益匪浅。同时面向对象的思想也给予我们更多设计方案。如同两次题目集不同的日期类设计。虽然在编写复杂度上题目集4-2大于题目集5-1,在清晰度和逻辑理解上题目集5-1也更高一筹,但是由于5-1在在设计上将太多的任务交给dateuial类处理,导致dateuial类呈现一种“上帝类”的状态,因为题目集4-2是聚合关系,题目集5-1三者之间没有直接关系,因此题目集5-1的耦合度较低,每次调用月和天增加和减少的方法都要额外判断进退位,没有简化问题,使题目集5-1的整体复杂度和峰值复杂度都过高,甚至是远高于题目集4-2。

  在经过第一、第二阶段的学习后,目前已经对Java有初步了解了。从一开始的对最基本的语法尚不熟悉,到第一阶段结束时逐步掌握Java新带来的一些如继承、多态等特性,和声明式编程内容正则表达式等,再到现在进一步了解更多如接口,以及更多面向对象思想和设计原则如开闭原则等等,对Java的理解的的确确是在不断加深的。比起一开始更多的把这门课当成“java语言程序设计”,现在在简单的完成PTA作业的情况外,也会更多的去考虑我们这门课真正的内容,即“面向对象程序设计”。

 

posted @ 2021-11-13 15:10  史隆长城  阅读(68)  评论(0)    收藏  举报