Java初步学习记录(pta作业集总结04-06)

Java初步学习记录

一、前言

以下开始针对(04-06)Java pta作业集做出以下总结( ̄▽ ̄):
这三次的题目集的难度明显上了一个档次,不再是花费一天两天的空余时间就能解决的了。首先是题目集04的菜单计价程序-3,由于初次接触这种比较大型的设计类题目,对于代码整体的逻辑性要求比较高,所以哪怕解决了基本的需求但测试bug也需要好久。其次是题目集05,难度不大,但需要对于类和类之间的关系有着比较好的理解才能比较快的解决。最后便是题目集06了,虽然只有一道题菜单计价程序-4,但其复杂程度较上个菜单提升了少至少一个档次,对于整体的设计合理性要求较高。因此不得不在JAVA的学习上多花费一些时间才能将这些题目集做好。(折磨的过程但确实学到了不少╮( ̄▽ ̄)╭)。

二、设计与分析

1、题目集04

除了7-1的菜单,其他大多都是比较简单的题目。所以此处只分析菜单计价程序-3,其他题目便不详细分析了。例如7-2的检查大量数据中是否有重复数据,只需要排序后在前后比较即可。7-3的去除重复数据可以直接用hashset解决,再将输出的格式改为题目要所需的输出格式即可,剩余的不在赘述。

7-1 菜单计价程序-3

作者 蔡轲 单位 南昌航空大学
题目描述:设计点菜计价程序,根据输入的信息,计算并输出总价格。

输入内容按先后顺序包括两部分:菜单、订单,最后以"end"结束。

菜单由一条或多条菜品记录组成,每条记录一行

每条菜品记录包含:菜名、基础价格 两个信息。

订单分:桌号标识、点菜记录和删除信息、代点菜信息。每一类信息都可包含一条或多条记录,每条记录一行或多行。

桌号标识独占一行,包含两个信息:桌号、时间。

桌号以下的所有记录都是本桌的记录,直至下一个桌号标识。

点菜记录包含:序号、菜名、份额、份数。份额可选项包括:1、2、3,分别代表小、中、大份。

不同份额菜价的计算方法:小份菜的价格=菜品的基础价格。中份菜的价格=菜品的基础价格1.5。小份菜的价格=菜品的基础价格2。如果计算出现小数,按四舍五入的规则进行处理。

删除记录格式:序号 delete

标识删除对应序号的那条点菜记录。

如果序号不对,输出"delete error"

代点菜信息包含:桌号 序号 菜品名称 份额 分数

代点菜是当前桌为另外一桌点菜,信息中的桌号是另一桌的桌号,带点菜的价格计算在当前这一桌。

程序最后按输入的先后顺序依次输出每一桌的总价(注意:由于有代点菜的功能,总价不一定等于当前桌上的菜的价格之和)。

每桌的总价等于那一桌所有菜的价格之和乘以折扣。如存在小数,按四舍五入规则计算,保留整数。

折扣的计算方法(注:以下时间段均按闭区间计算):

周一至周五营业时间与折扣:晚上(17:00-20:30)8折,周一至周五中午(10:30--14:30)6折,其余时间不营业。

周末全价,营业时间:9:30-21:30

如果下单时间不在营业范围内,输出"table " + t.tableNum + " out of opening hours"

参考以下类的模板进行设计:菜品类:对应菜谱上一道菜的信息。

Dish {

String name;//菜品名称

int unit_price; //单价

int getPrice(int portion)//计算菜品价格的方法,输入参数是点菜的份额(输入数据只能是1/2/3,代表小/中/大份) }

菜谱类:对应菜谱,包含饭店提供的所有菜的信息。

Menu {

Dish[] dishs ;//菜品数组,保存所有菜品信息

Dish searthDish(String dishName)//根据菜名在菜谱中查找菜品信息,返回Dish对象。

Dish addDish(String dishName,int unit_price)//添加一道菜品信息

}

点菜记录类:保存订单上的一道菜品记录

Record {

int orderNum;//序号\

Dish d;//菜品\

int portion;//份额(1/2/3代表小/中/大份)\

int getPrice()//计价,计算本条记录的价格\

}

订单类:保存用户点的所有菜的信息。

Order {

Record[] records;//保存订单上每一道的记录

int getTotalPrice()//计算订单的总价

Record addARecord(int orderNum,String dishName,int portion,int num)//添加一条菜品信息到订单中。

delARecordByOrderNum(int orderNum)//根据序号删除一条记录

findRecordByNum(int orderNum)//根据序号查找一条记录

}

输入格式:

桌号标识格式:table + 序号 +英文空格+ 日期(格式:YYYY/MM/DD)+英文空格+ 时间(24小时制格式: HH/MM/SS)

菜品记录格式:

菜名+英文空格+基础价格

如果有多条相同的菜名的记录,菜品的基础价格以最后一条记录为准。

点菜记录格式:序号+英文空格+菜名+英文空格+份额+英文空格+份数注:份额可输入(1/2/3), 1代表小份,2代表中份,3代表大份。

删除记录格式:序号 +英文空格+delete

代点菜信息包含:桌号+英文空格+序号+英文空格+菜品名称+英文空格+份额+英文空格+分数

最后一条记录以“end”结束。

输出格式:

按输入顺序输出每一桌的订单记录处理信息,包括:

1、桌号,格式:table+英文空格+桌号+“:”

2、按顺序输出当前这一桌每条订单记录的处理信息,

每条点菜记录输出:序号+英文空格+菜名+英文空格+价格。其中的价格等于对应记录的菜品*份数,序号是之前输入的订单记录的序号。如果订单中包含不能识别的菜名,则输出“** does not exist”,**是不能识别的菜名

如果删除记录的序号不存在,则输出“delete error”

最后按输入顺序一次输出每一桌所有菜品的总价(整数数值)格式:table+英文空格+桌号+“:”+英文空格+当前桌的总价

本次题目不考虑其他错误情况,如:桌号、菜单订单顺序颠倒、不符合格式的输入、序号重复等,在本系列的后续作业中会做要求

输入样例:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9
table 1 2023/3/22 12/2/3
1 麻婆豆腐 2 2
2 油淋生菜 1 3
end 

输出样例:

在这里给出相应的输出。例如:

table 1: 
1 麻婆豆腐 36
2 油淋生菜 27
table 1: 38

输入样例 1:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9
table 1 2023/3/22 17/0/0
1 麻婆豆腐 2 2
2 油淋生菜 1 3
1 delete
end

输出样例 1:

在这里给出相应的输出。例如:

table 1: 
1 麻婆豆腐 36
2 油淋生菜 27
table 1: 22

输入样例 2:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9
table 1 2023/3/22 16/59/59
1 麻婆豆腐 2 2
2 油淋生菜 1 3
1 delete
end

输出样例 2:

在这里给出相应的输出。例如:

table 1: 
1 麻婆豆腐 36
2 油淋生菜 27
table 1 out of opening hours

输入样例 3:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9
table 1 2022/12/5 15/03/02
1 麻婆豆腐 2 2
2 油淋生菜 1 3
3 麻辣鸡丝 1 2
5 delete
7 delete
table 2 2022/12/3 15/03/02
1 麻婆豆腐 2 2
2 油淋生菜 1 3
3 麻辣鸡丝 1 2
7 delete
end

输出样例 3:

在这里给出相应的输出。例如:

table 1: 
1 麻婆豆腐 36
2 油淋生菜 27
麻辣鸡丝 does not exist
delete error;
delete error;
table 2: 
1 麻婆豆腐 36
2 油淋生菜 27
麻辣鸡丝 does not exist
delete error;
table 1 out of opening hours
table 2: 63

输入样例 4:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9
table 1 2022/12/3 19/5/12
1 麻婆豆腐 2 2
2 油淋生菜 1 3
3 麻辣鸡丝 1 2
table 2 2022/12/3 15/03/02
1 麻婆豆腐 2 2
2 油淋生菜 1 3
3 麻辣鸡丝 1 2
1 4 麻婆豆腐 1 1
7 delete
end

输出样例 4:

在这里给出相应的输出。例如:

table 1: 
1 麻婆豆腐 36
2 油淋生菜 27
麻辣鸡丝 does not exist
table 2: 
1 麻婆豆腐 36
2 油淋生菜 27
麻辣鸡丝 does not exist
4 table 2 pay for table 1 12
delete error;
table 1: 63
table 2: 75
代码长度限制                              16 KB  
时间限制                                 400 ms
内存限制                                  64 MB  

Steps:碰到较大的设计类题目,首先需要考虑的便是类的设计,不过此处已经给好了所需的类和方法,算是走出了第一步,所以后续便是思考给定的类之间是如何联系的,其中的参数,返回值类型为何要如此设计等,通过题目要求把上述问题思考好,再将这些类及方法先设计完,最后再设计主方法。因此,类的设计最好考虑完备不能出现bug,否则后续主方法中的问题便不太好找了。以下是该题目对于的类图:

Ideas:顺着题目所给出的类一步步分析并设计完善,最后再完成主方法的设计。开始先看到最为基础的Dish类设计,其中对应属性和方法的注释都已经给出,故比较简单,需要注意的便是单价乘以份额的计算应当四舍五入

class Dish{
    String name;//菜品名称
    int unit_price; //单价
    public Dish() {
    }
    public Dish(String name, int unit_price) {
        this.name = name;
        this.unit_price = unit_price;
    }
    public int getPrice(int portion){//portion点菜份额1小/2中/3大
        int price = 0;
        if(portion == 1){
            price = this.unit_price;
        } else if (portion == 2) {
            price = (int) (this.unit_price * 1.5 + 0.5);  //四舍五入的常用方法
        } else if (portion == 3) {
            price = this.unit_price * 2;
        }
        return price;
    }
}

其次Menu类的设计,明显和Dish为聚合关系,而其中的Dish数组需要注意初始化的问题,可以将其改为ArrayList来解决数组扩容的问题,或者初次定义较大的数值给该数组也行,本人使用的较笨的手动扩容方法,至于为什么笨,后续主方法中便可知晓╮( ̄▽ ̄)╭。而后我们可以发现,其添加方法返回Dish类对象,其实是为了数组实际存储的数据的个数的后一位直接进行赋值操作。因为在方法内部进行数组增添操作不大方便。

class Menu{
    Dish[] dishes;//菜品数组,保存所有菜品信息
    public Menu() {
        this.dishes = new Dish[0];//初始化数组
    }
    public Dish searthDish(String dishName) {//根据菜名在菜谱中查找菜品信息,返回Dish对象。
        for(int i = 0;i <dishes.length;i++ ){
            if(dishes[i].name.equals(dishName)){
                return dishes[i];
            }
        }
        return null;//查找失败返回空对象
    }
    public Dish addDish(String dishName,int unit_price){//添加一道菜品信息
        return new Dish(dishName,unit_price);//返回new出的对象
    }
}

随后是Record类的设计,没有什么需要注意的点,按照要求设计即可。其中注意其中包含的Dish类属性,在构造Record对象时注意一并将其中的Dish类也进行构造。

class Record{
    int orderNum;//序号
    Dish d;//菜品
    int portion;//份额(1/2/3代表小/中/大份)
    int number;//份数
    public Record() {
    }
    public Record(int orderNum, String dishName, int portion, int number) {
        this.d = new Dish();
        this.orderNum = orderNum;
        this.d.name = dishName;
        this.portion = portion;
        this.number = number;
    }
    public int getPrice() {//计价,计算本条记录的价格
        return d.getPrice(portion)*number;
    }
}

最后是Order类的设计,其中包含了Record对象数组。addARecord方法同Menu中的addDish方法。随后是delete方法,其中的返回值个人建议是int类型或者boolean类型,方便后续判断是否删除成功,不过本人当时设计时以为是void类型的,所以判断是否删除成功的输出也放在方法内部了(其实这样不好(〜 ̄△ ̄)〜)。然后是find方法,一般思路便是遍历一遍查找后返回对应的下标即可,失败便返回-1。

class Order{
    Record[] records;//保存订单上每一道的记录
    public Order() {
        this.records = new Record[0];
    }
    public int getTotalPrice() {//计算订单的总价
        int allPrice = 0;
        for (int i = 0;i < records.length;i++ ){
            allPrice += records[i].getPrice();
        }
        return allPrice;
    }
    public Record addARecord(int orderNum,String dishName,int portion,int num) {//添加一条菜品信息到订单中。
        Record newrecord = new Record();
        newrecord.d = new Dish();
        newrecord.orderNum = orderNum;
        newrecord.d.name = dishName;
        newrecord.portion = portion;
        newrecord.number = num;
        return newrecord;
    }
    public void delARecordByOrderNum(int orderNum) {//根据序号删除一条记录
        int index = findRecordByNum(orderNum);
        if (index == -1){
            System.out.println("delete error;");
        }else {
            for (int i = index; i < records.length; i++) {
                if (i != records.length-1) {
                    records[i] = records[i + 1];
                }
            }
        }
    }
    public int findRecordByNum(int orderNum) {//根据序号查找一条记录
        for (int i = 0;i < records.length;i++){
            if (records[i].orderNum == orderNum){
                return i;
            }
        }
        return -1;
    }
}

随着类的设计完善,后续便是分析题目的输入输出设计主方法了。首先我们需要创建Menu对象。然后我们可以看到,输入是具有格式的,总共有6种格式的输入,而每一种格式的输入都会有不同的效果,所以我们需要通过对输入信息格式的判断进行不同的处理方式。由于此时还未学习正则表达式,因此只能通过split分割的字符串共多少组以及对应的一些标志性字符串来判断属于哪一类的输入。例如删除信息的判断,通过" "分割出的字符串有两组,并且第二组字符串为"delete",从而完成对于删除信息的判断。至于输入的停止既可以放在循环内部,也可以放在循环条件处。

下面便从删除信息开始,将对于输入信息的处理代码写出。删除信息的第一部分为删除的记录序号,因此我们需要查找Order种是否存在该条记录,因此使用order种的find方法查找对应记录,而后如果查找失败则返回-1并输出"delete error"。否则便将调用order种的del方法对于查找的记录号进行删除操作。而后将order种records数组容量-1.其中tablePrice为记录截至目前各个桌所花费的金额。

if ("delete".equals(splitScan[1])) { //如果输入格式为delete则进行记录删除
    int index = order.findRecordByNum(Integer.parseInt(splitScan[0]));
    if(index == -1){
        System.out.println("delete error;");
    } else {
        tablePrice[tablePrice.length-1] -= order.records[index].getPrice();
        order.delARecordByOrderNum(Integer.parseInt(splitScan[0]));
        order.records = Arrays.copyOf(order.records,order.records.length-1);
}        

随后是菜谱信息的录入,其判断标准为分割出的字符串组共两组且没有"delete"标志字符串。而后先new一个Dish对象接受输入的菜品信息,由于可能存在相同的菜品名录入,所以需要使用菜品中的search方法查找对应的菜品名是否已经在菜谱中,存在便进行覆盖操作,不存在则扩容后进行赋值操作。

if (splitScan.length == 2 && !"delete".equals(splitScan[1])){  //如果格式为第一种菜品名+菜品单价
    Dish newDish = menu.addDish(splitScan[0],Integer.parseInt(splitScan[1]));
    if (menu.searthDish(newDish.name) == null){  //查询新增的菜品是否已经在菜单中
        menu.dishes = Arrays.copyOf(menu.dishes,menu.dishes.length+1); //对对应菜单的扩容
        menu.dishes[menu.dishes.length-1] = newDish;  //将新增的菜品加入菜单末尾
    }else{
        menu.searthDish(newDish.name).unit_price = newDish.unit_price; //已经存在的话就改变对应单价
    }
}

而后为较为麻烦的桌号信息的录入,判断标准为分割出的字符串组有四组且第一组字符串等同字符串"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]);  //获得对应的分钟
}

之后是普通记录信息的录入,其判断标准为分割的字符串组共四组且没有"table"标志。记录信息的录入需要优先判断对应的菜品名称是否存在,存在则先让new出的Record对象中的d等同于再菜谱中查找出的菜品。随后输出对应记录的信息并存储花费的价格即可。并将该记录添加至order的records中。如果查找失败,则按照题目要求输出即可。

if (splitScan.length == 4){ //如果输入格式为点菜信息,录入点菜信息
    Record newRecord = order.addARecord(Integer.parseInt(splitScan[0]),splitScan[1],
            Integer.parseInt(splitScan[2]),Integer.parseInt(splitScan[3]));
    if (menu.searthDish(splitScan[1]) == null){  //判断订单中的菜品在菜单中是否存在
        System.out.println(splitScan[1]+" does not exist");
    }else {
        newRecord.d = menu.searthDish(splitScan[1]);
        order.records = Arrays.copyOf(order.records, order.records.length + 1); //对对应订单的扩容
        order.records[order.records.length - 1] = newRecord;  //将新增的点菜记录加到订单末尾
        System.out.println(newRecord.orderNum+" "+newRecord.d.name+" "+ newRecord.getPrice());
        tablePrice[tablePrice.length-1] += newRecord.getPrice();
    }
}

再者为代点菜信息的录入,其判断标准为分割出的字符串数组共五组。随后同普通记录的处理相似,先判断菜品名是否存在于菜谱中。不同之处在于代点菜信息输出的信息和普通记录不同,其中的价格也是算在目前点菜的一桌上。

if (splitScan.length == 5) {  //如果输入为代点菜信息,录入代点菜信息
    Record newRecord = new Record(Integer.parseInt(splitScan[1]),splitScan[2],
            Integer.parseInt(splitScan[3]),Integer.parseInt(splitScan[4]));
    if (menu.searthDish(splitScan[2]) == null) {  //判断订单中的菜品在菜单中是否存在
        System.out.println(splitScan[1] + " does not exist");
    }else{
        newRecord.d = menu.searthDish(newRecord.d.name);
        int otherPrice = getAllPrice(newRecord.getPrice(),week[week.length-1],hour[hour.length-1],min[min.length-1]);
        tablePrice[tablePrice.length-1] += newRecord.getPrice();
        System.out.println(splitScan[1]+" table " + tableNumber[tableNumber.length-1] +" pay for table "+splitScan[0]+" "+otherPrice);
    }
}

最后对于对应桌的总花费进行单独输出,放在循环外部。其中time用于判断时间是否再营业时间内,返回值为boolean类型。而getAllPrice为通过时间判断折扣的方法。(其实二者可以合并为一个方法解决问题(⌒▽⌒))

for (int i = 0;i < tablePrice.length;i++) {
    if (tablePrice[tablePrice.length - 1] != 0) {
        if (time(week[i], hour[i], min[i])) {
            System.out.println("table" + " " +tableNumber[i]+": "+ getAllPrice(tablePrice[i],week[i], hour[i], min[i]));
        } else {
            System.out.println("table" + " " + tableNumber[i] + " out of opening hours");
        }
    }
}

time方法的代码如下

public static boolean time(int week,int hour,int min){
    if (week >= 1 && week <= 5){
        if (hour >= 17){
            if (hour < 20){
                return true;
            }else if(hour == 20 && min <= 30){
                return true;
            }
        }else if (hour > 9) {
            if (hour == 10 && min >= 30) { //此处判断有问题,需要简单的更改一下,例如10:20的结果仍然为true
                return true;
            }
            if (hour < 14) {
                return true;
            } else if (hour == 14 && min <= 30) {
                return true;
            }
        }
    } else {
        if (hour >= 9){
            if (hour == 9){
                if (min < 30){
                    return false;
                }else
            }
            if (hour < 21){
                return true;
            }else if(hour == 21 && min <= 30){
                return true;
            }
        }
    }
    return false;
}

至此该题目已经基本解决,可惜由于题目时间限制在400ms以内导致一些点因为运行超时只能得21分。不过也还行了,如果不通过这种扩容方式,而更改为ArrayList则可完美解决这一问题。

2、题目集05

相较于上次的04,不包括菜单那道题目的话整体而言是更简单一点的,但最后两道类的设计依旧需要耗费不少时间。此外便是正则表达式的一些基本练习,没什么需要注意的点。此处我便稍微提一下7-5、7-6日期类的聚合设计。

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

作者 段喜龙 单位 南昌航空大学
题目描述:参考题目7-2的要求,设计如下几个类:DateUtil、Year、Month、Day,其中年、月、日的取值范围依然为:year∈[1900,2050] ,month∈[1,12] ,day∈[1,31] , 设计类图如下:

应用程序共测试三个功能:

  1. 求下n天
  2. 求前n天
  3. 求两个日期相差的天数

注意:严禁使用Java中提供的任何与日期相关的类与方法,并提交完整源码,包括主类及方法(已提供,不需修改)

输入格式:

有三种输入方式(以输入的第一个数字划分[1,3]):

  • 1 year month day n //测试输入日期的下n天
  • 2 year month day n //测试输入日期的前n天
  • 3 year1 month1 day1 year2 month2 day2 //测试两个日期之间相差的天数

输出格式:

  • 当输入有误时,输出格式如下:
Wrong Format
  • 当第一个数字为1且输入均有效,输出格式如下:
  year-month-day
  • 当第一个数字为2且输入均有效,输出格式如下:
  year-month-day
  • 当第一个数字为3且输入均有效,输出格式如下:
  天数值

输入样例1:

在这里给出一组输入。例如:

3 2014 2 14 2020 6 14

输出样例1:

在这里给出相应的输出。例如:

2312

输入样例2:

在这里给出一组输入。例如:

2 1935 2 17 125340

输出样例2:

在这里给出相应的输出。例如:

1591-12-17

输入样例3:

在这里给出一组输入。例如:

1 1999 3 28 6543

输出样例3:

在这里给出相应的输出。例如:

2017-2-24

输入样例4:

在这里给出一组输入。例如:

0 2000 5 12 30

输出样例4:

在这里给出相应的输出。例如:

Wrong Format
代码长度限制                              12 KB  
时间限制                                 10000 ms
内存限制                                  64 MB  

Steps:对于已经给好类图的题目设计,其实只要按照类图上的各个类逐一设计即可,注意其中的单一聚合关系,优先设计没有单一聚合关系的类,然后再逐一向上设计。
Ideas:由于大部分方法比较简单,此处便主要分析利用聚合关系后一些方法的改变。先是Month类中的monthIncrease方法和Reduction方法,二者比较相像,所以此处只分析Increase方法。先判断此时的Month是否等于12,如果是则重置month的值而后调用year中的yearIncrease方法即可。

public void monthIncrease() {
    if (this.month == 12){
        resetMin();
        year.yearIncrement();
    }else {
        this.month++;
    }
}

而后是Day中的dayIncrease方法和Reduction方法,同上分析Increase方法即可。先进行闰年的判断,调整其中mon_maxnu[1]的值,随后判断day是否为此时monh的最大值,如果相等,则调用month中的Increase方法,并重置day即可。否则day++。

public void dayIncrement() {
    if (this.month.getYear().isLeapYear()){
        this.mon_maxnum[1] = 29;
    }else {
        this.mon_maxnum[1] = 28;
    }
    if (this.day == this.mon_maxnum[this.month.getMonth()-1]){
        this.month.monthIncrease();
        resetMin();
    }else {
        day++;
    }
}

之后是DateUtil中的getNextNDays和getPreviousNDays方法,同样只分析一个即可。只需要通过循环,每次调用day中的Increase方法即可,判断条件为n>0

public DateUtil getPreviousNDays(int n) {
    while (n > 0){
        this.day.dayReduction();
        n--;
    }
    return this;
}

至此该题目便大致完成了,我们容易发现,使用单一聚合关系后DateUtil中的代码长度能减少很多,代码的复用性明显提高了,对于单一聚合关系能明显简化程序结构,降低复杂度。

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

作者 段喜龙 单位 南昌航空大学
题目描述:参考题目7-3的要求,设计如下几个类:DateUtil、Year、Month、Day,其中年、月、日的取值范围依然为:year∈[1820,2020] ,month∈[1,12] ,day∈[1,31] , 设计类图如下:

应用程序共测试三个功能:

  1. 求下n天
  2. 求前n天
  3. 求两个日期相差的天数

注意:严禁使用Java中提供的任何与日期相关的类与方法,并提交完整源码,包括主类及方法(已提供,不需修改)

输入格式:

有三种输入方式(以输入的第一个数字划分[1,3]):

  • 1 year month day n //测试输入日期的下n天
  • 2 year month day n //测试输入日期的前n天
  • 3 year1 month1 day1 year2 month2 day2 //测试两个日期之间相差的天数

输出格式:

  • 当输入有误时,输出格式如下:
Wrong Format
  • 当第一个数字为1且输入均有效,输出格式如下:
  year1-month1-day1 next n days is:year2-month2-day2
  • 当第一个数字为2且输入均有效,输出格式如下:
  year1-month1-day1 previous n days is:year2-month2-day2
  • 当第一个数字为3且输入均有效,输出格式如下:
  The days between year1-month1-day1 and year2-month2-day2 are:值

输入样例1:

在这里给出一组输入。例如:

3 2014 2 14 2020 6 14

输出样例1:

在这里给出相应的输出。例如:

The days between 2014-2-14 and 2020-6-14 are:2312

输入样例2:

在这里给出一组输入。例如:

2 1834 2 17 7821

输出样例2:

在这里给出相应的输出。例如:

1834-2-17 previous 7821 days is:1812-9-19

输入样例3:

在这里给出一组输入。例如:

1 1999 3 28 6543

输出样例3:

在这里给出相应的输出。例如:

1999-3-28 next 6543 days is:2017-2-24

输入样例4:

在这里给出一组输入。例如:

0 2000 5 12 30

输出样例4:

在这里给出相应的输出。例如:

Wrong Format
代码长度限制                              12 KB  
时间限制                                 10000 ms
内存限制                                  64 MB  

Steps:对于已经给好类图的题目设计,其实只要按照类图-上的各个类逐一设计即可。相较于7-5将单一的聚合关系改为了一对多的聚合关系,但其本质并没什么变化,所以直接按照类图上的方法先将Year,Month,Day类设计完成后,再进行DateUtil类的设计。
Ideas:由于Year,Month,Day类种的方法比较简单,此处便主要分析DateUtil类种的方法。首先是其中的getNextDays和getPreviousNDays方法,照常只分析第一个方法。优先进行闰年的判断,随后看day的值是否到达了该月最大值,再判断month的值是否到达了该年最大值,再分别处理。如果以上条件均不满足,调用day种的Increase方法即可。

public DateUtil getNextNDays(int n) {
    while (n > 0){
        if (this.year.isLeapYear()){
            this.mon_maxnum[1] = 29;
        }else {
            this.mon_maxnum[1] = 28;
        }
        if (this.day.getDay() == this.mon_maxnum[this.month.getMonth()-1]){
            if (this.month.getMonth() == 12){
                this.year.yearIncrement();
                this.month.resetMin();
            }else {
                this.month.monthIncrease();
            }
            this.setDayMin();
        }else {
            this.day.dayIncrement();
        }
        n--;
    }
    return this;
}

而后是getDaysofDates方法,基本和最开始日期类设计的代码一致,先判断大小关系,随后分别用自带的getDaysofDates或this.getNextNDays方法使自身的Date下移或上移一天,知道二者日期相同则退出循环返回n的值。

public int getDaysofDates(DateUtil date) {
    int n = 0;
    if (this.compareDates(date)) {
        while (!this.equalTwoDates(date)) {
            this.getPreviousNDays(1);
            n++;
        }
    }else {
        while (!this.equalTwoDates(date)) {
            this.getNextNDays(1);
            n++;
        }
    }
    return n;
}

至此该题目便基本完成了,一对多的聚合关系虽然会让DateUtil类中的方法复杂化,但方便了数据的组织与管理,同时因为每个模块都是独立的,从而更方便了程序功能的可扩展性,修改某个类并不会影响到其他类。

3、题目集06

这次的题目集06难度上升了一个大档次,就算只有一道题目菜单计价程序-4。如果写过菜单计价程序-3的话,写起来会有个框架,节省不少时间。不然直接接触菜单计价程序-4比较容易摸不着头脑,思路不太好捋清。

7-4 菜单计价程序-4

作者 蔡轲 单位 南昌航空大学
题目描述:本体大部分内容与菜单计价程序-3相同,增加的部分用加粗文字进行了标注。
本次课题比菜单计价系列-3增加的异常情况:

1、菜谱信息与订单信息混合,应忽略夹在订单信息中的菜谱信息。输出:"invalid dish"

2、桌号所带时间格式合法(格式见输入格式部分说明,其中年必须是4位数字,月、日、时、分、秒可以是1位或2位数),数据非法,比如:2023/15/16 ,输出桌号+" date error"

3、同一桌菜名、份额相同的点菜记录要合并成一条进行计算,否则可能会出现四舍五入的误差。

4、重复删除,重复的删除记录输出"deduplication :"+序号。

5、代点菜时,桌号不存在,输出"Table number :"+被点菜桌号+" does not exist";本次作业不考虑两桌记录时间不匹配的情况。

6、菜谱信息中出现重复的菜品名,以最后一条记录为准。

7、如果有重复的桌号信息,如果两条信息的时间不在同一时间段,(时段的认定:周一到周五的中午或晚上是同一时段,或者周末时间间隔1小时(不含一小时整,精确到秒)以内算统一时段),此时输出结果按不同的记录分别计价。

8、重复的桌号信息如果两条信息的时间在同一时间段,此时输出结果时合并点菜记录统一计价。前提:两个的桌号信息的时间都在有效时间段以内。计算每一桌总价要先合并符合本条件的饭桌的点菜记录,统一计价输出。

9、份额超出范围(1、2、3)输出:序号+" portion out of range "+份额,份额不能超过1位,否则为非法格式,参照第13条输出。

10、份数超出范围,每桌不超过15份,超出范围输出:序号+" num out of range "+份数。份数必须为数值,最高位不能为0,否则按非法格式参照第16条输出。

11、桌号超出范围[1,55]。输出:桌号 +" table num out of range",桌号必须为1位或多位数值,最高位不能为0,否则按非法格式参照第16条输出。

12、菜谱信息中菜价超出范围(区间(0,300)),输出:菜品名+" price out of range "+价格,菜价必须为数值,最高位不能为0,否则按非法格式参照第16条输出。

13、时间输入有效但超出范围[2022.1.1-2023.12.31],输出:"not a valid time period"

14、一条点菜记录中若格式正确,但数据出现问题,如:菜名不存在、份额超出范围、份数超出范围,按记录中从左到右的次序优先级由高到低,输出时只提示优先级最高的那个错误。

15、每桌的点菜记录的序号必须按从小到大的顺序排列(可以不连续,也可以不从1开始),未按序排列序号的输出:"record serial number sequence error"。当前记录忽略。(代点菜信息的序号除外)

16、所有记录其它非法格式输入,统一输出"wrong format"

17、如果记录以“table”开头,对应记录的格式或者数据不符合桌号的要求,那一桌下面定义的所有信息无论正确或错误均忽略,不做处理。如果记录不是以“table”开头,比如“tab le 55 2023/3/2 12/00/00”,该条记录认为是错误记录,后面所有的信息并入上一桌一起计算。

本次作业比菜单计价系列-3增加的功能:

菜单输入时增加特色菜,特色菜的输入格式:菜品名+英文空格+基础价格+"T"

例如:麻婆豆腐 9 T

菜价的计算方法:

周一至周五 7折, 周末全价。

注意:不同的四舍五入顺序可能会造成误差,请按以下步骤累计一桌菜的菜价:

计算每条记录的菜价:将每份菜的单价按份额进行四舍五入运算后,乘以份数计算多份的价格,然后乘以折扣,再进行四舍五入,得到本条记录的最终支付价格。

最后将所有记录的菜价累加得到整桌菜的价格。

输入格式:

桌号标识格式:table + 序号 +英文空格+ 日期(格式:YYYY/MM/DD)+英文空格+ 时间(24小时制格式: HH/MM/SS)

菜品记录格式:

菜名+英文空格+基础价格

如果有多条相同的菜名的记录,菜品的基础价格以最后一条记录为准。

点菜记录格式:序号+英文空格+菜名+英文空格+份额+英文空格+份数注:份额可输入(1/2/3), 1代表小份,2代表中份,3代表大份。

删除记录格式:序号 +英文空格+delete

代点菜信息包含:桌号+英文空格+序号+英文空格+菜品名称+英文空格+份额+英文空格+分数

最后一条记录以“end”结束。

输出格式:

按输入顺序输出每一桌的订单记录处理信息,包括:

1、桌号,格式:table+英文空格+桌号+”:”

2、按顺序输出当前这一桌每条订单记录的处理信息,

每条点菜记录输出:序号+英文空格+菜名+英文空格+价格。其中的价格等于对应记录的菜品*份数,序号是之前输入的订单记录的序号。如果订单中包含不能识别的菜名,则输出“** does not exist”,**是不能识别的菜名

如果删除记录的序号不存在,则输出“delete error”

最后按输入顺序一次输出每一桌所有菜品的总价(整数数值)格式:table+英文空格+桌号+“:”+英文空格+当前桌的原始总价+英文空格+当前桌的计算折扣后总价

输入样例:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9 T
table 31 2023/2/1 14/20/00
1 麻婆豆腐 1 16
2 油淋生菜 1 2
2 delete
2 delete
end

输出样例:

在这里给出相应的输出。例如:

table 31: 
1 num out of range 16
2 油淋生菜 18
deduplication 2
table 31: 0 0

输入样例 1:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9 T
table 31 2023/2/1 14/20/00
1 麻婆豆腐 1 16
2 油淋生菜 4 2
end

输出样例 1:

在这里给出相应的输出。例如:

table 31: 
1 num out of range 16
2 portion out of range 4
table 31: 0 0

输入样例 2:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9 T
table a 2023/3/15 12/00/00
1 麻婆豆腐 1 1
2 油淋生菜 2 1
end

输出样例 2:

在这里给出相应的输出。例如:

table 1: 
1 麻婆豆腐 36
2 油淋生菜 27
table 1 out of opening hours

输入样例 3:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9 T
table 55 2023/3/31 12/000/00
麻辣香锅 15
1 麻婆豆腐 1 1
2 油淋生菜 2 1
end

输出样例 3:

在这里给出相应的输出。例如:

wrong format

输入样例 4:

在这里给出一组输入。例如:

麻婆豆腐 12.0
油淋生菜 9 T
table 55 2023/3/31 12/00/00
麻辣香锅 15
1 麻婆豆腐 1 1
2 油淋生菜 2 1
end

输出样例 4:

在这里给出相应的输出。例如:

wrong format
table 55: 
invalid dish
麻婆豆腐 does not exist
2 油淋生菜 14
table 55: 14 10

输入样例 5:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9 T
table a 2023/3/15 12/00/00
1 麻婆 豆腐 1 1
2 油淋生菜 2 1
end

输出样例 5:

在这里给出相应的输出。例如:

wrong format

输入样例 6:

在这里给出一组输入。例如:

麻婆豆腐 12
油淋生菜 9 T
table 1 2023/3/15 12/00/00
1 麻婆豆腐 1 1
2 油淋生菜 2 1
tab le 2 2023/3/15 12/00/00
1 麻婆豆腐 1 1
2 油淋生菜 2 1
end

输出样例 6:

在这里给出相应的输出。例如:

table 1: 
1 麻婆豆腐 12
2 油淋生菜 14
wrong format
record serial number sequence error
record serial number sequence error
table 1: 26 17
代码长度限制                              50 KB  
时间限制                                 1000 ms
内存限制                                  64 MB  

Steps:此处并不分析全部情况,只挑取个别个人认为比较重要的点讲解即可。首先需要明白这次由于情况复杂多了,所以判断的条件也要增强,故而将之前的判断改成了正则表达式进行具体判断。随后是特价菜的加入,需要在Dish类中增添一种属性用于判断是否为特价菜。再者便是对应的输入输出因为存在忽略和"wrong format"的情况,所以需要对原代码中输入信息的判断进行大改,类图如下:

Ideas:首先是6种基本信息输入时需要的正则表达式,代码如下。当然,根据题目要求,这些都是基础判断的正则表达式。

regex[0] = "[\\u4e00-\\u9fa5]+ \\d+(\\.\\d+)?( T)?";
regex[1] = "\\d+ delete";
regex[2] = "table[\\s\\S]+";
regex[3] = "\\d+ [\\u4e00-\\u9fa5a-zA-Z]+ \\d+ \\d+";
regex[4] = "\\d+ \\d+ [\\u4e00-\\u9fa5a-zA-Z]+ \\d+ \\d+";
regex[5] = "end";

而后分析题目的几个新添要求。此处分析在桌号信息符合要求的情况下如何处理之后的输入。
1.忽略订单中的菜谱信息,可以在录入桌号信息后增添一个循环结构,并再次对其中的输入信息依次判断,如果是菜谱类型的输入则按照题目要求格式输出即可。
2.删除信息的处理,由于不能重复删除,因而可以添加一个deleteNumber数组用于存储所删除记录的序号。
3.普通记录信息的处理,除去原先的代码外,需要添加对于各种不符合格式或者范围的输入的条件判断。而且需要按照格式正确-菜品存在-份额正常-份数正常的顺序依次判断。全部正确的情况下,通过遍历order中records数组里orderNumber,从而检查是否满足这条记录的序号小于前面记录的序号。
4.代点菜信息的处理,需要增添一个对于桌号是否存在的判断,通过tableNumber数组存取桌号而后遍历即可,其他的大多同普通记录一致,注意代点菜输出的代付金额是未打折前的金额(之前被这个坑死了o( ̄ヘ ̄o#))
5.end信息或者桌号信息的处理,直接退出循环结构即可。

while ( !scan.matches(regex[2]) && !scan.matches(regex[5]) ){
    if ( scan.matches(regex[0]) ){
        System.out.println("invalid dish");
    } else if ( scan.matches(regex[1]) ){  //删除记录
        int flag = 0;
        for (int i = 0; i < deleteNumber.length; i++) {  //查找所删除记录是否出现过
            if (Integer.parseInt(splitScan[0]) == deleteNumber[i]){
                flag = 1;
                System.out.println("deduplication " + deleteNumber[i]);
                break;
            }
        }
        if (flag == 0) {
            int index = orders[orders.length - 1].findRecordByNum(Integer.parseInt(splitScan[0]));
            if (index == -1) {
                System.out.println("delete error");
            } else {
                deleteNumber = Arrays.copyOf(deleteNumber, deleteNumber.length + 1);
                deleteNumber[deleteNumber.length - 1] = orders[orders.length - 1].records[index].orderNum;
                orders[orders.length - 1].delARecordByOrderNum(Integer.parseInt(splitScan[0]));
                orders[orders.length - 1].records = Arrays.copyOf(orders[orders.length - 1].records, orders[orders.length - 1].records.length - 1);
            }
        }
    } else if ( scan.matches(regex[3]) ) {  //普通记录
        Record newRecord = orders[orders.length - 1].addARecord(Integer.parseInt(splitScan[0]),splitScan[1],
                Integer.parseInt(splitScan[2]),Integer.parseInt(splitScan[3]));
        if ( !scan.matches("\\d+ [\\u4e00-\\u9fa5a-zA-Z]+ \\d \\d+") ){
            System.out.println("wrong format");
        }else if ( !splitScan[3].matches("\\d|[1-9]\\d") ){
            System.out.println("wrong format");
        }else if (menu.searthDish(splitScan[1]) == null){  //判断订单中的菜品在菜单中是否存在
            System.out.println(splitScan[1]+" does not exist");
        }else if ( !newRecord.d.special && !splitScan[2].matches("[1-3]") ){
            System.out.println(newRecord.orderNum + " portion out of range " + newRecord.portion);
        }else if (newRecord.d.special && !splitScan[2].matches("[1-2]")){
            System.out.println(newRecord.orderNum + " portion out of range " + newRecord.portion);
        }else if ( newRecord.number > 15){
            System.out.println(newRecord.orderNum + " num out of range " + newRecord.number);
        }else {
            int flag = 0;
            for (int i = 0; i < orders[orders.length - 1].records.length; i++) {
                if (newRecord.orderNum <= orders[orders.length - 1].records[i].orderNum) {
                    System.out.println("record serial number sequence error");
                    flag = 1;
                    break;
                }
            }
            if (flag == 0) {
                newRecord.d = menu.searthDish(splitScan[1]);
                //同一桌份额菜名相同的情况
                for (int i = 0; i < orders[orders.length - 1].records.length; i++) {
                    if (newRecord.d.name.equals(orders[orders.length - 1].records[i].d.name) && newRecord.portion == orders[orders.length - 1].records[i].portion){
                        orders[orders.length - 1].records[i].number += newRecord.number;
                        break;
                    } else if (i == orders[orders.length - 1].records.length - 1) {
                        orders[orders.length - 1].records = Arrays.copyOf(orders[orders.length - 1].records, orders[orders.length - 1].records.length + 1); //对对应订单的扩容
                        orders[orders.length - 1].records[orders[orders.length - 1].records.length - 1] = newRecord;  //将新增的点菜记录加到订单末尾
                        break;
                    }
                }
                if (orders[orders.length - 1].records.length == 0) {
                    orders[orders.length - 1].records = Arrays.copyOf(orders[orders.length - 1].records, orders[orders.length - 1].records.length + 1); //对对应订单的扩容
                    orders[orders.length - 1].records[orders[orders.length - 1].records.length - 1] = newRecord;  //将新增的点菜记录加到订单末尾
                }
                System.out.println(newRecord.orderNum + " " + newRecord.d.name + " " + newRecord.getPrice());
            }
        }
    } else if ( scan.matches(regex[4]) ) {  //代点菜记录
        Record newRecord = new Record(Integer.parseInt(splitScan[1]),splitScan[2],
                Integer.parseInt(splitScan[3]),Integer.parseInt(splitScan[4]));
        int index = -1;  //看代点菜桌号是否存在
        for (int i = 0; i < tableNumber.length - 1; i++) {
            if (tableNumber[i] == Integer.parseInt(splitScan[0])){
                index = i;
                break;
            }
        }
        if ( index != -1 ) {
            if (menu.searthDish(splitScan[2]) == null) {  //判断订单中的菜品在菜单中是否存在
                System.out.println(splitScan[1] + " does not exist");
            } else {
                newRecord.d = menu.searthDish(newRecord.d.name);
                for (int i = 0; i < orders[orders.length - 1].records.length; i++) {
                    if (newRecord.d.name.equals(orders[orders.length - 1].records[i].d.name) && newRecord.portion == orders[orders.length - 1].records[i].portion){
                        orders[orders.length - 1].records[i].number += newRecord.number;
                        break;
                    } else if (i == orders[orders.length - 1].records.length - 1) {
                        orders[orders.length - 1].records = Arrays.copyOf(orders[orders.length - 1].records, orders[orders.length - 1].records.length + 1); //对对应订单的扩容
                        orders[orders.length - 1].records[orders[orders.length - 1].records.length - 1] = newRecord;  //将新增的点菜记录加到订单末尾
                        break;
                    }
                }
                if (orders[orders.length - 1].records.length == 0) {
                    orders[orders.length - 1].records = Arrays.copyOf(orders[orders.length - 1].records, orders[orders.length - 1].records.length + 1); //对对应订单的扩容
                    orders[orders.length - 1].records[orders[orders.length - 1].records.length - 1] = newRecord;  //将新增的点菜记录加到订单末尾
                }
                int otherPrice = getDiscountPrice(newRecord, week[week.length - 1], hour[hour.length - 1], min[min.length - 1]);
                System.out.println(splitScan[1] + " table " + tableNumber[tableNumber.length - 1] + " pay for table " + splitScan[0] + " " + newRecord.getPrice());
            }
        }
    }
}

随后便是如何确定对应折扣后的价格并输出了,个人感觉可以直接在原先营业时间的判断方法上修改即可,直接将返回值类型改成int类型,如果判断后时间出口在周一至周五中午返回6,下午返回8,周六周天时间内返回10,否则不在时间内返回-1。这样便可利用其返回值来进行对应折扣。注意:特色菜需要优先判断并返回7,因为特色菜并不参与时间的折扣。下面给出时间判断和获取折扣价的方法。

public static int time(int week,int hour,int min){  //判断时间的方法
    if (week >= 1 && week <= 5){
        if(hour >= 17 && hour < 20 || (hour == 20 && min <= 30)){
            return 8;
        }
        if(hour >= 11 && hour < 14 || (hour == 14 && min <= 30) || (hour == 10 && min >= 30)){
            return 6;
        }
    } else {
        if(hour >= 10 && hour < 21 || (hour == 9 && min >= 30) || (hour == 21 && min <= 30)){
            return 10;
        }
    }
    return -1;
}
public static int getDiscountPrice(Record record,int week,int hour,int min){  //获得折扣价的方法
    if ( record.d.special && time(week, hour, min) != 10){ //特色菜的情况
        return (int)(record.getPrice() * 0.7 + 0.5);
    }else {
        if (time(week, hour, min) == 10){
            return record.getPrice();
        }else if (time(week, hour, min) == 8){
            return (int)(record.getPrice() * 0.8 + 0.5);
        }else if (time(week, hour, min) == 6){
            return (int)(record.getPrice() * 0.6 + 0.5);
        }else{
            return 0;
        }
    }
}

最后是输出的方式,个人直接通过循环结构输出结果即可。

for (int i = 0;i < tablePrice.length;i++) {
    if (time(week[i], hour[i], min[i]) != -1) {
        System.out.println("table" + " " +tableNumber[i]+": "+ tablePrice[i] + " " + discountTablePrice[i]);
    } else {
        System.out.println("table" + " " + tableNumber[i] + " out of opening hours");
    }
}

基本方法都已经给出,虽然还有很多没有分析,但也差不多了。其中最难的点为重复桌号需要调整输出顺序的要求,因此需要定义字符串数组来存取输出的信息,时间不够就放弃了,不过测试点足够多,少点分也问题不大╮( ̄▽ ̄)╭。

三、踩坑心得

题目集04

7-1菜单计价程序-3

其中对于时间的检查方法存在一些错误,事实上想找出这样细微的错误其实很麻烦,我将二十四个小时的时间基本都试了个遍才发现问题。

而修改后代码如下

public static boolean time(int week,int hour,int min){
    if (week >= 1 && week <= 5){
        if (hour >= 17){
            if (hour < 20){
                return true;
            }else if(hour == 20 && min <= 30){
                return true;
            }
        }else if (hour > 9) {
            if (hour == 10) {
                if(min >= 30){
                    return true;
                }else{
                    return false;
                }
            }
            if (hour < 14) {
                return true;
            } else if (hour == 14 && min <= 30) {
                return true;
            }
        }
    } else {
        if (hour >= 9){
            if (hour == 9){
                if (min < 30){
                    return false;
                }
            }
            if (hour < 21){
                return true;
            }else if(hour == 21 && min <= 30){
                return true;
            }
        }
    }
    return false;
}

题目集06

7-1菜单计价程序-4

开始由于存在各种bug只有67分,而第一个找出的bug便是不合法时间数据的输入时输出不匹配。如下所示

原代码如下所示

if ( scan.matches(regexTable) && splitScan[1].matches("\\d|[1-9]\\d")) {
    if( Integer.parseInt(splitScan[1]) < 0 || Integer.parseInt(splitScan[1]) > 55){  //桌号超出范围的情况
        System.out.println(Integer.parseInt(splitScan[1]) + " table num out of range");
        break;
    }
    System.out.println("table" + " " + splitScan[1] + ": ");//问题在此处
    String[] splitDate = splitScan[2].split("/");
    String[] splitTime = splitScan[3].split("/");
    //检查时间数据是否合法
    if (checkDateValidity(Integer.parseInt(splitDate[0]), Integer.parseInt(splitDate[1]), Integer.parseInt(splitDate[2]),
            Integer.parseInt(splitTime[0]), Integer.parseInt(splitTime[1]), Integer.parseInt(splitTime[2]))) {
    }
}

后续修改的代码如下所示

 if ( scan.matches(regexTable) && splitScan[1].matches("\\d|[1-9]\\d")) {
    if( Integer.parseInt(splitScan[1]) < 0 || Integer.parseInt(splitScan[1]) > 55){  //桌号超出范围的情况
        System.out.println(Integer.parseInt(splitScan[1]) + " table num out of range");
        break;
    }
    String[] splitDate = splitScan[2].split("/");
    String[] splitTime = splitScan[3].split("/");
    //检查时间数据是否合法
    if (checkDateValidity(Integer.parseInt(splitDate[0]), Integer.parseInt(splitDate[1]), Integer.parseInt(splitDate[2]),
            Integer.parseInt(splitTime[0]), Integer.parseInt(splitTime[1]), Integer.parseInt(splitTime[2]))) {
        System.out.println("table" + " " + splitScan[1] + ": ");//修改的地方
    }
 }

修改后便不会出现table 31:的输出了,直接上涨了10分( ̄▽ ̄)。
后续又修改了两处,一处时时间合法性的检验有点问题,还有一处是特色菜的价格计算有点问题。

原代码如下所示

public static boolean checkDateValidity(int year,int month,int day,int hour,int min,int sec){
    int[] mon_maxnum=new int[]{0,31,28,31,30,31,30,31,31,30,31,30,31};
    if (hour > 24 || hour < 0){ //问题所在
        return false;
    }
} 

public static int getDiscountPrice(Record record,int week,int hour,int min){
    if ( record.d.special && time(week, hour, min) != 10){ //特色菜的情况(问题所在)
        return (int)(record.getPrice() * 0.7 + 0.5);
    }
}

后续修改的代码如下所示

public static boolean checkDateValidity(int year,int month,int day,int hour,int min,int sec){
    int[] mon_maxnum=new int[]{0,31,28,31,30,31,30,31,31,30,31,30,31};
    if (hour > 23 || hour < 0){ //问题所在
        return false;
    }
} 

public static int getDiscountPrice(Record record,int week,int hour,int min){
    if ( record.d.special){ //特色菜的情况(问题所在)
        return (int)(record.getPrice() * 0.7 + 0.5);
    }
}

修改完后问题完美解决,有一说一bug是真的难找,最好的办法还是分模块测试,不然功能一多放在一起实在太难查出问题了。改完后又成功的上涨了10分φ(-ω-*)。

四、改进建议

个人认为最大可以改进的地方自然是将7-1菜单计价程序中的一个个扩容的方法改为ArrayList类型,利用其自带的size方法得到此时有效数据的个数。其次便是可以更改其中时间判断的方法,将原先的分开判断更改为合并判断,如下所示。

public static int time(int week,int hour,int min){  //判断时间的方法
    if (week >= 1 && week <= 5){
        if(hour >= 17 && hour < 20 || (hour == 20 && min <= 30)){
            return 8;
        }
        if(hour >= 11 && hour < 14 || (hour == 14 && min <= 30) || (hour == 10 && min >= 30)){
            return 6;
        }
    } else {
        if(hour >= 10 && hour < 21 || (hour == 9 && min >= 30) || (hour == 21 && min <= 30)){
            return 10;
        }
    }
    return -1;
}

改进过后的代码明显更清晰明了,逻辑性更为紧密。
而后是改为Arraylist后的Order类,如下所示

class Order{
    ArrayList<Record> records;//保存订单上每一道的记录
    public Order() {
        this.records = new ArrayList<Record>();
    }
    public int getTotalPrice() {//计算订单的总价
        int allPrice = 0;
        for (int i = 0;i < records.size();i++ ){
            allPrice += records.get(i).getPrice();
        }
        return allPrice;
    }
    public Record addARecord(int orderNum,String dishName,int portion,int num) {//添加一条菜品信息到订单中。
        Record newrecord = new Record();
        newrecord.d = new Dish();
        newrecord.orderNum = orderNum;
        newrecord.d.name = dishName;
        newrecord.portion = portion;
        newrecord.number = num;
        records.add(newrecord);
        return newrecord;
    }
    public void delARecordByOrderNum(int orderNum) {//根据序号删除一条记录
        int index = findRecordByNum(orderNum);
        if (index == -1){
            System.out.println("delete error;");
        }else {
            records.remove(index);
        }
    }
    public int findRecordByNum(int orderNum) {//根据序号查找一条记录
        for (int i = 0;i < records.size();i++){
            if (records.get(i).orderNum == orderNum){
                return i;
            }
        }
        return -1;
    }
}

改进后使用ArrayList类型的好处如下:1.动态改变数组大小,不需要手动维护数组大小。2.ArrayList提供了更多的方法,使用更加方便。3.可以不用考虑数组下标越界的问题。4.可以通过ArrayList提供的方法方便的进行遍历、查找、删除等操作。

五、总结

由于题目集难度的逐步提升,所花费的时间越来越多了,再加上课程本身的不断推进,确实有点难顶(〜 ̄△ ̄)〜。不得不承认的是确实学到的也不少,对于类之间关系的理解进一步加深了,而且知道了在面对不确定的复杂的输入情况时该如何处理(不完全知道X﹏X)。而对于整体程序的设计熟练度也有所提升。同时也认真学习并了解了JAVA中非常强大的正则表达式,为日后更为复杂的程序设计打下基础。对于近期的作业量,我感觉还是有一点点多,也不至于太多。只能说,基本是被推着一路跑过来的(针不戳(┯_┯)),后续再接再厉吧o( ̄ヘ ̄o#)。

posted @ 2023-04-30 10:16  混日子的小羊  阅读(110)  评论(2)    收藏  举报
返回顶端