题目集1~3总结

DAVIDZ-ZWD / 2023-05-23 / 原文

1.前言

对题目集的总结:

1.知识点

对于前三次练习的知识点,我觉得三次各自的知识点是突出,鲜明的。

第一次:显然是考察 选择结构程序(if-else)和输入输出格式,对输入的数据进行判断然后进行相应的操作再进行相应的格式的输出。

第二次:显然是考察 循环结构程序设计(for() ; while() ; )与java类的构建与使用,例如7-1 菜单计价程序-1与7-2 菜单计价程序-2,重点都是用循环处理输入的数据和直接使用Java已有类或构建相应类的数据域进行对相应数据的储存和构造相应方法实现相应功能,然后达成输出答案的目的。

第三次:第1,6,7题主要考察的还是对输入数据的判断后的处理以及对java日期的使用,有承接第二次练习。第2,3题考察一些容器如具有除重功能的set,第4题的话是利用一些容器的排序功能。

2.题量

个人认为三次练习的题量适中偏多吧,在难度适宜的情况下是可以达到锻炼能力的作用的(个人认为重点在于难度的控制,难度不适宜,刷多少题都没有用)。

3.难度

个人认为第一第二次练习难度适宜,但第三次练习的第一题虽然难度不是特别难,但有出格不当或者很难的地方,因为它并没有很好的和第二次练习衔接好,题目的要求明显变多,但题目给的类却一直未变,依然停留在原始的版本,以题目的桌子需要建立一个Table类为例,题目并没有给出来说要建立这个类,虽然容易自己去想着建立,但对于一些刚入门Java学习较慢的同学,是很难去自己思考出需要新增的类的,大部分人是第一次接触类这个概念,如果这次没建立Table类那么后面的几次点菜练习将会难度大大增加导致挫败而彻底脱节,也就是说,题目难度出格就出格在没有十分正确地引导学生建立正确的类,向模范的代码暗示引导

2.设计与分析

重点分析一些偏项目类(点菜-1,2,3)的题目,(由于本蒟蒻博主要打icpc省赛,时间不太充裕)其他偏算竞类的题目暂不分析;本文重点内容加粗处理。

1.菜单计价程序-1

我的代码:

import java.util.Scanner;
public class Main {
    public static void main(String a[]) {
//        准备菜单
        Menu menu=new Menu();
        Dish d=new Dish("西红柿炒蛋",15);
        menu.add(d);
        d=new Dish("清炒土豆丝",12);
        menu.add(d);
        d=new Dish("麻婆豆腐",12);
        menu.add(d);
        d=new Dish("油淋生菜",9);
        menu.add(d);
        Order order=new Order(menu);
        
        /*String dishName="麻婆豆腐";
        int portion=2;
        order.addARecord(dishName, portion);*/
        Scanner in=new Scanner(System.in);
        String name;
        int p;
        name=in.next();
        while(!name.equals("end"))
        {
            p=in.nextInt();
            order.addARecord(name,p);
            //System.out.println(name+" "+p);
            name=in.next();
        }
        System.out.println((int)(order.getTotalPrice()+0.5));
    }
}

//菜品类:对应菜谱上一道菜的信息。
class Dish {
    String name;// 菜品名称
    int unit_price; // 单价
    Dish(String name,int price)
    {
        this.name=name;
        this.unit_price=price;
    }
    float getPrice(int portion)// 计算菜品价格的方法,输入参数是点菜的份额(输入数据只能是1/2/3,代表小/中/大份)
    {
        float bl[]= {1,1.5f,2};
        return unit_price*bl[portion-1];
    }
}

//菜谱类:对应菜谱,包含饭店提供的所有菜的信息。
class Menu {
    public Dish[] dishs=new Dish[4];// 菜品数组,保存所有菜品信息
    static int cnt=0;
    public void add(Dish dish)//菜单加一道菜
    {
        dishs[cnt++]=dish;
    }
    Dish searthDish(String dishName)// 根据菜名在菜谱中查找菜品信息,返回Dish对象。
    {
        for(int i=0;i<4;i++)
        {
            if(dishName.equals(dishs[i].name))
            {
                return dishs[i];
            }
        }
        return null;
    }
}

//点菜记录类:保存订单上的一道菜品记录
class Record {
    Dish d;// 菜品
    int portion;// 份额(1/2/3代表小/中/大份)
    public Record(Dish d,int portion)
    {
        this.d=d;
        this.portion=portion;
    }
    float getPrice()// 计价,计算本条记录的价格
    {
        return d.getPrice(portion);
    }
}

//订单类:保存用户点的所有菜的信息。
class Order {
    Record[] records=new Record[100];// 保存订单上每一道的记录
    Menu menu;
    static int cnt2=0;
    float getTotalPrice()// 计算订单的总价
    {
        float total=0;
        if(records==null)
            return 0;
        for(int i=0;i<cnt2;i++)
        {
            total+=records[i].getPrice();
          //  System.out.println(records[i].getPrice());
        }
        return total;
    }
    public Order(Menu menu)
    {
        this.menu=menu;
    }
    //根据菜名点菜
    Record addARecord(String dishName, int portion) {
       Record r=new Record(menu.searthDish(dishName),portion);
        if(r.d==null)
        {
            System.out.println(dishName+" does not exist");
        }
        else
            records[cnt2++]=r;
        return records[cnt2];
    }
}

我的类图:

 我的分析:对于点菜-1这道题,其实对很多学生来说意义重大,因为他是我们第一道真正意义上面向对象的题。也之所以因为它是被称为面向对象的题,它的重点在于构建合适的类,最后根据题意构建类与类之间正确的关联关系,最后为类创立实现相应功能的方法,最后实现点菜-1的要求功能。

2.菜单计价程序-2 

我的代码:

import java.util.Scanner;
public class Main {
    public static void main(String a[]) {
//        准备菜单
        Menu menu=new Menu();
        Dish d;
        Scanner in=new Scanner(System.in);
        String s1;
        s1=in.next();
        while(!s1.equals("end"))
        {
            if(s1.charAt(0)<'1'||s1.charAt(0)>'9')
            {
                float price;
                price=in.nextFloat();
                d=new Dish(s1,price);
                menu.add(d);
            }
            else
            {
                break;
            }
            s1=in.next();
        }
        Order order=new Order(menu);
        String name;
        int portion;
        int num;
        while(!s1.equals("end"))
        {
            int orderNum=Integer.parseInt(s1.substring(0,s1.length()));
            name=in.next();
            if(name.equals("delete"))
            {
                order.delARecordByOrderNum(orderNum);
                s1=in.next();
            }
            else
            {
                portion=in.nextInt();
                num=in.nextInt();
                order.addARecord(orderNum,name,portion,num);
                s1=in.next();
            }
        }
        //if((int)(order.getTotalPrice()+0.5)!=0)
            System.out.println((int)(order.getTotalPrice()+0.5));
    }
}

//菜品类:对应菜谱上一道菜的信息。
class Dish {
    String name;// 菜品名称
    float unit_price; // 单价
    Dish(String name,float price)
    {
        this.name=name;
        this.unit_price=price;
    }
    float getPrice(int portion)// 计算菜品价格的方法,输入参数是点菜的份额(输入数据只能是1/2/3,代表小/中/大份)
    {
        float bl[]= {1,1.5f,2};
        return unit_price*bl[portion-1];
    }
}

//菜谱类:对应菜谱,包含饭店提供的所有菜的信息。
class Menu {
    public Dish[] dishs=new Dish[1000000];// 菜品数组,保存所有菜品信息
    static int cnt=0;
    public void add(Dish dish)//菜单加一道菜
    {
        dishs[cnt++]=dish;
    }
    Dish searthDish(String dishName)// 根据菜名在菜谱中查找菜品信息,返回Dish对象。
    {
        Dish find=null;
        for(int i=cnt-1;i>=0;i--)
        {
            if(dishName.equals(dishs[i].name))
            {
                find=dishs[i];//返回最新的
                break;
            }
        }
        return find;
    }
}

//点菜记录类:保存订单上的一道菜品记录
class Record {
    int orderNum;
    Dish d;// 菜品
    int portion;// 份额(1/2/3代表小/中/大份)
    int num;
    int f=0;
    public Record(int orderNum,Dish d,int portion,int num)
    {
        this.orderNum=orderNum;
        this.d=d;
        this.portion=portion;
        this.num=num;
    }
    float getPrice()// 计价,计算本条记录的价格
    {
        if(d!=null)
            return ((int)(d.getPrice(portion)+0.5))*num;
        else
            return 0;
    }
}

//订单类:保存用户点的所有菜的信息。
class Order {
    Record[] records=new Record[1000000];// 保存订单上每一道的记录
    Menu menu;
    static int cnt2=0;
    float getTotalPrice()// 计算订单的总价
    {
        float total=0;
        if(records==null)
            return 0;
        for(int i=0;i<cnt2;i++)
        {
            if(records[i].f==0)
                total+=(int)(records[i].getPrice()+0.5);
            //System.out.println(records[i].orderNum+" "+records[i].d.name+" "+(int)(records[i].getPrice()+0.5));
        }
        return total;
    }
    public Order(Menu menu)
    {
        this.menu=menu;
    }
    //根据菜名点菜
    Record addARecord(int orderNum,String dishName, int portion,int num) 
    {
       Record r=new Record(orderNum,menu.searthDish(dishName),portion,num);
        if(r.d==null)
        {
            records[cnt2++]=r;//菜单没有这道菜,点菜记录依然要记录?
            System.out.println(dishName+" does not exist");
        }
        else
        {
            records[cnt2++]=r;
            System.out.println(r.orderNum+" "+r.d.name+" "+(int)(r.getPrice()+0.5));
        }
        return records[cnt2];
    }
    void delARecordByOrderNum(int orderNum)//根据序号删除一条记录
    {
        int i=0;
        for(i=0;i<cnt2;i++)
        {
            if(records[i].orderNum==orderNum) 
            {
                //if(records[i].f==1)
                //{
                //    System.out.println("delete error;");
               // }
                records[i].f=1;
                return ;
            }
        }
        if(i==cnt2) 
            System.out.println("delete error;");
    }
}

我的类图:

 我的分析:点菜-2与点菜-1相比,不过是增加了删除功能,对于类并没有很大影响,所以实现功能的话,我们主要还是考虑在一个类增加具有删除功能的函数,比较点菜-2与点菜-1的类图可以发现点菜-2的Order类中新增了delARecordByOrderNum()函数,他的功能便是删除点菜记录。所以我的评价是实现某个单调的功能的话,主要考虑在合适的类中创建相应功能的函数,而对于某种具有多种功能且具有某种性质的事物,我的建议是创建一个类,比如下面的点菜3。

3.菜单计价程序-3

我的代码:

import java.text.ParseException;
import java.util.*;
import java.util.Scanner;

public class Main {
public static void main(String[] args){
Scanner in = new Scanner(System.in);
List<Table> tables = new ArrayList<>();

Menu menu = new Menu();
String line = in.nextLine();
Table curTable = null;

while (!line.equals("end")) {
String[] parts = line.split(" ");

if (parts.length > 2) {
if ("table".equals(parts[0])) {
Table table = new Table();
tables.add(table);
table.setTime(parts[2]);
table.setIndex(parts[1]);
table.setDayTime(parts[3]);
table.setOrder(new Order(menu, table));

curTable = table;
} else if (parts.length == 4) {
int orderNum = Integer.parseInt(parts[0]);
String dishName = parts[1];
int portion = Integer.parseInt(parts[2]);
int num = Integer.parseInt(parts[3]);

assert curTable != null;
curTable.getOrder().addARecord(orderNum, dishName, portion, num, curTable.getIndex(), curTable.getIndex());
} else if (parts.length == 5) {
for (Table table : tables) {
if (table.getIndex().equals(parts[0])) {
// 代點菜
int orderNum = Integer.parseInt(parts[1]);
String dishName = parts[2];
int portion = Integer.parseInt(parts[3]);
int num = Integer.parseInt(parts[4]);

table.getOrder().addARecord(orderNum, dishName, portion, num, curTable.getIndex(), parts[0]);
curTable.getOrder().addARecord(orderNum, dishName, portion, num, curTable.getIndex(), parts[0]);
break;
}
}
}
} else if ("delete".equals(parts[1])) {
assert curTable != null;
curTable.getOrder().delARecordByOrderNum(Integer.parseInt(parts[0]));
} else {
menu.addDish(parts[0], Integer.parseInt(parts[1]));
}
line = in.nextLine();
}

for (Table table : tables) {
System.out.println("table " + table.getIndex() + ": ");
Order order = table.getOrder();
for (Record record : order.getRecords()) {
String from = record.getFrom();
String to = record.getTo();
if (record.isAddError()) {
System.out.println(record.getD().getName() + " does not exist");
} else if (record.isDelError()) {
System.out.println("delete error;");
} else if (from.equals(table.getIndex())) {
if (to.equals(table.getIndex())) {
System.out.println(record.getOrderNum() + " " + record.getD().getName() + " " + record.getPrice());
} else {
System.out.println(record.getOrderNum() + " table " + from + " pay for table " + to + " " + record.getPrice());
}
}
}
}
for (Table table : tables) {
Order order = table.getOrder();
int sale = table.getSale();
if (sale > 0) {
System.out.println("table " + table.getIndex() + ": " + Math.round(order.getTotalPrice() * sale / 10f));
} else {
System.out.println("table " + table.getIndex() + " out of opening hours");
}
}
}
}

class Table {
private String index;
private String dayTime;
private String time;
private Order order;

public String getIndex() {
return index;
}

public void setIndex(String index) {
this.index = index;
}
public void setDayTime(String dayTime) {
this.dayTime = dayTime;
}

public void setTime(String time) {
this.time = time;
}

public Order getOrder() {
return order;
}

public void setOrder(Order order) {
this.order = order;
}

private Calendar getTimeCalendar(String time) {
String[] arr = time.split("/");
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(arr[0]));
cal.set(Calendar.MINUTE, Integer.parseInt(arr[1]));
cal.set(Calendar.SECOND, Integer.parseInt(arr[2]));
return cal;
}

public int getSale() {
String[] dayArray = time.split("/");
Calendar calendar = Calendar.getInstance();

 

calendar.set(Calendar.DAY_OF_MONTH, 32);
//
// calendar.set(Calendar.YEAR,Integer.parseInt(dayArray[0]));
// calendar.set(Calendar.MONTH, Integer.parseInt(dayArray[1]) - 1);
// calendar.set(Calendar.DATE,Integer.parseInt(dayArray[2]));
calendar.set(Integer.parseInt(dayArray[0]), Integer.parseInt(dayArray[1])-1, Integer.parseInt(dayArray[2]));

Calendar now = getTimeCalendar(dayTime);

int weekDay = calendar.get(Calendar.DAY_OF_WEEK) - 1;

if (weekDay == 0 || weekDay == 6) {
if (!now.before(getTimeCalendar("9/30/0")) && !now.after(getTimeCalendar("21/00/00"))) {
return 10;
}
} else {
if (!now.before(getTimeCalendar("17/00/00")) && !now.after(getTimeCalendar("20/30/00"))) {
return 8;
}
if (!now.before(getTimeCalendar("10/30/00")) && !now.after(getTimeCalendar("14/30/00"))) {
return 6;
}
}
// 表示不营业
return 0;
}
}

class Menu {
private final Map<String, Dish> dishs = new HashMap<>();//菜品数组,保存所有菜品信息

Menu() {
}

Dish searchDish(String dishName) {
return dishs.get(dishName);
}

//添加一道菜品信息
void addDish(String dishName, int unit_price) {
Dish dish = new Dish(dishName, unit_price);
dishs.put(dishName, dish);
}
}

class Dish {
String name;//菜品名称
int unit_price; //单价

public Dish(String dishName) {
this.name = dishName;
}

public String getName() {
return name;
}


public Dish(String name, int unit_price) {
this.name = name;
this.unit_price = unit_price;
}

//计算菜品价格的方法,输入参数是点菜的份额(输入数据只能是1/2/3,代表小/中/大份)
int getPrice(int portion) {
float b[] = {1, 1.5f, 2};
return Math.round((unit_price * b[portion - 1]));
}
}

class Record {
private int orderNum;//序号\
private Dish d;//菜品\
private int portion;//份额(1/2/3代表小/中/大份)\
private int num;
private String from;
private String to;
private boolean isDelete = false;
private boolean isAddError = false;
private boolean isDelError = false;

public Record() {
}

public Record(int orderNum, Dish d, int portion, int num, String from, String to) {
this.orderNum = orderNum;
this.d = d;
this.portion = portion;
this.num = num;
this.from = from;
this.to = to;
}

//计价,计算本条记录的价格
int getPrice() {
return d.getPrice(portion) * this.num;
}

public int getOrderNum() {
return orderNum;
}

public Dish getD() {
return d;
}

public void setD(Dish d) {
this.d = d;
}

public boolean isDelete() {
return isDelete;
}

public void setDelete(boolean delete) {
isDelete = delete;
}

public boolean isAddError() {
return isAddError;
}

public void setAddError(boolean addError) {
isAddError = addError;
}

public boolean isDelError() {
return isDelError;
}

public void setDelError(boolean delError) {
isDelError = delError;
}

public String getFrom() {
return from;
}

public String getTo() {
return to;
}

public void setTo(String to) {
this.to = to;
}
}

class Order {
private Menu menu;
private Table table;

public List<Record> getRecords() {
return records;
}

private final List<Record> records = new ArrayList<>();//保存订单上每一道的记录

public Order(Menu menu, Table table) {
this.menu = menu;
this.table = table;
}

//计算订单的总价
int getTotalPrice() {
int sum = 0;
for (Record record : records) {
if (!record.isDelete() && !record.isDelError() && !record.isAddError() && table.getIndex().equals(record.getFrom())) {
int price = record.getPrice();
sum = sum + price;
}
}

return sum;
}

//添加一条菜品信息到订单中。
void addARecord(int orderNum, String dishName, int portion, int num, String from, String to) {
Dish dish = menu.searchDish(dishName);
if (dish == null && table.getIndex().equals(from)) {
Record record = new Record();
record.setD(new Dish(dishName));
record.setAddError(true);
records.add(record);
return;
}
Record record = new Record(orderNum, dish, portion, num, from, to);
records.add(record);
}

public void delARecordByOrderNum(int orderNum) {
for (Record record : records) {
if (record.getOrderNum() == orderNum) {
record.setDelete(true);
return;
}
}
Record record = new Record();
record.setDelError(true);
records.add(record);
}
}

 

我的类图:

 

 我的分析:

承接上题的分析:"所以我的评价是实现某个单调的功能的话,主要考虑在合适的类中创建相应功能的函数,而对于某种具有多种功能且具有某种性质的事物,我的建议是创建一个类,比如下面的点菜-3。" 。

点菜-3相比点菜-2,它的变化不小,最重要的是输入的变化,新增了桌子信息的输入,桌号标识格式:table + 序号 +英文空格+ 日期(格式:YYYY/MM/DD)+英文空格+ 时间(24小时制格式: HH/MM/SS)。

1.稍微分析一下输入这类数据的意义

1.对于时间,它对价格的计算有了新的要求,对桌子是否在营业时间有判断要求

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

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

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

2.对于桌号

它有带点菜功能的实现:代点菜信息包含:桌号+英文空格+序号+英文空格+菜品名称+英文空格+份额+英文空格+分数

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

根据这两点,我们清楚地知道,我们要的,是一个不仅具有桌号,时间属性的事物,而且这个事物还要能根据他的时间实现相应功能,比如求折扣。所以与其苦恼于创建多个复杂的函数,我们应当去建立一个Table类,这个类不仅有时间和桌号数据,还要有能实现一些功能的函数(详见类图),值得一提的是,对于Table类的时间数据储存和便于后续使用,我们可以利用Java 日历类进行预处理然后储存。有了Table类之后,我们对题目的功能要求分析与处理会更加有序,层次分明。

2.知道输入数据的意义,那么怎么高效确定输入的信息是什么呢?也就是说怎么知道输入的是菜品信息,还是桌号信息,还是点菜信息,还是带点菜信息呢?

许多同学只知道split()函数来把一行信息拆开为多部份,然后根据各部分,确定输入的是什么信息。但这样不仅操作麻烦,而且会存在疏漏。

这里介绍一种快速确定输入的一行数据是哪类信息的方法:正则表达式。

设输入的一行数据以String的类型接收在line中

line.matches("[\\S]* [1-9][\\d]*")则代表菜品信息,"[\\S]* [1-9][\\d]*"的含义是格式为 "字符串+首位数字为1到9的整数",显然指的是菜品信息。

而对于其他比如桌子信息 line.matches("table.*")  在点菜-3中,则可直接判断是否是桌子。

其他类型数据套娃即可。

3.踩坑心得

1.所有人都必踩的坑之一,输出精度问题。

将浮点数定义为double(结果一般为float,虽然我不知道为什么不定义为精度更高的double),然后输出的时候答案由于精度问题导致答案错误

以第一次练习7-2为例

输入的数据是浮点数,如果用double类型接受,那么它打印输出的精度更大(小数点后保留位数更多与答案的float保留的位数不同)导致答案错误。

所以要在输出的进行强制类型转换为float,和答案的精度对上!

import java.util.Scanner;
public class Main{
    public static void main(String[] args){
        Scanner input=new Scanner(System.in);
        double m=input.nextDouble();
        double l=input.nextDouble();
        System.out.print((float)(m/0.45359237)+" "+(float)(l/0.0254));
    }
}

2.其他各种格式的语法问题不再赘述,但对于使用c++打算法竞赛的同学来说,值得一提的是java的map是不支持map[key]=v这样访问的。。。

4.主要困难和改进建议

相信几乎绝大对部分同学认为最难的一部分就是点菜计价程序了,我们绝大多数人遇到的困难是:

1.不能合理地根据题意抽象出相应的类。

2.面对大量的输入,和多要求输出,不能做到恰当的判断和分类处理。

3.对于题目的一些异常特判点,无法完全通过或理解导致失分。

对于这三个主要困难

我有对自己地建议,也有对出题者的建议。

首先,是对自己的建议:

对于问题一,我个人的还是那句话:根据题意,分析出需要储存的数据和功能,如果我们需要的某种功能是独立,那么可以单独创建一个方法实现该功能,如果我们需要的不是某种到单独的功能,而是多个功能与某些具有普遍性的性质结合的话,那么应该考虑创建一个类。

对于问题二,对于输入数据的判断其表达的信息,无非是与题目所给的格式进行匹配,格式相匹配,则是题目对应的信息类型。所以重点在于对输入的数据进行格式匹配:方法有两种1.正则表达式mathch()匹配。2.split()拆分成多部份进行判断。

对于问题三,说实话这是最折磨我的一点,因为我的分数多失于这里,迄今我也没有合适的方法去解决一些特殊测试点。但我觉得一些测试点可以用过题目已经给的测试样例推敲出来,这样能起到一些作用。

然后,是对出题者的建议:

对于问题一,我个人认为在初次引入某个信息时,有必要做充足的指引去引导学生去创建正确的类,还是拿第三题规范代码要创建Table类为例,题目的提示并不够明显,经过我的了解,不少同学依然是用之前的类硬着油皮实现题目要求的功能。所以我自以为有必要对新出现的信息做足够的介绍与暗示,才能更好的引导学生养成正确代码规范。

对于问题二,出于题目所给的输入数据比较有代表性,所以并没有什么建议,=。

对于问题三,我个人强烈建议在每次练习结束后公布测试点,因为对于一些特殊的测试点,不少人确实思考几个小时也想不出到底错在哪,如果不公布测试点,他们永远也不知道怎么改进自己错误的代码,而只会去看别人已经过了的代码,强制接受别人东西而并未改进自己,进而导致下一次练习,面临相同的特殊测试点依然不知道怎么处理。

5.总结

多的不说,其实学习Java这门面向对象的语言,目前最让我收获最大的是就是学会创建自己类进行结合和使用一些库类,学会利用类与类之间的关联,进而实现多要求的功能。至于一些特殊容器比如map,set的话,实话实说感觉还是c++用的顺手.........。