一、前言
(1)题量适中,与难度有关,三次题目集都在4-7题左右,对我而言,题量适合,能够在一周内完成。
(2)相关知识点:前三次题目集基本覆盖了大部分java基础语法及其用法,以及java中的类及其中的方法,如LocalDate类、Hash类等。
此外,还涉及了java三大特性之一的封装。(具体涉及的知识点我会在分析题目的时候总结)在刚刚入门时,为了完成这三次作业,我利用CSDN查阅了许多资料,相关类及其用法。在完成这三次题目集之后,自身java语言的编程能力的基础得到了一定的提升。从对类一知半解到能够理解并初步运用。
二、设计与分析
(1)第一次题目集中题目比较简单,许多地方与C类似,最需要注意的就是在写代码时,要注意精度,否则会影响最后的结果。
(2)在第二次题目集的7-1(即菜单计价程序-1)中,因题目要求简单,不用类也可满足,于是在这一题没有使用类,以下给出第二次题目集7-1(即菜单计价程序7-1)的代码及SourceMonitor的生成报表以供参考:
1 import java.util.ArrayList; 2 import java.util.Scanner; 3 public class Main{ 4 public static void main(String[] args) { 5 Scanner data = new Scanner(System.in); 6 7 ArrayList<Integer> perPrice = new ArrayList<Integer>(); 8 //将菜品信息储存在数组中 9 String[] dish = new String[]{"西红柿炒蛋", "清炒土豆丝", "麻婆豆腐", "油淋生菜"}; 10 int[] price = new int[]{15, 12, 12, 9}; 11 12 //点菜 13 while (true) { 14 //输入菜名 15 String dishName = data.next(); 16 17 if (dishName.equals("end")) 18 break; 19 //判断是否输入end 20 21 //输入份额 22 int portion = data.nextInt(); 23 int flag = 5; 24 //查找输入的菜名是否存在 25 for (int i = 0; i < 4; i++) { 26 if (dishName.equals(dish[i])) 27 flag = i; 28 //如果找到该菜名,则让flag=i; 29 } 30 if (flag == 5) { 31 System.out.println(dishName + " " + "does not exist"); 32 }//该菜名不存在 33 else{ 34 int p; 35 switch (portion) { 36 case 1: 37 p = price[flag]; 38 perPrice.add(p); 39 break; 40 case 2: 41 p = (int) (price[flag] * 1.5 + 0.5); 42 perPrice.add(p); 43 break; 44 case 3: 45 p = price[flag] * 2; 46 perPrice.add(p); 47 break; 48 } 49 } 50 } 51 int totalPrice=0; 52 for (int i = 0; i < perPrice.size(); i++) { 53 totalPrice += perPrice.get(i); 54 } 55 System.out.println(totalPrice); 56 } 57 58 }
由图分析可知,不用类来解决问题,在平均每个函数包含的语句数目(Average Statements per Function)、平均深度(Avg Depth)等均超出良好(绿色)范围,这样的代码虽然能够通过并拿到满分,但是其实际上是不合格的,是不符合代码规范的。但因题目要求较简单,这道题不用类来解决还可以通过,但是在菜单计价程序-2中,不采用类来写是非常复杂的,会让整个代码非常繁琐和复杂,代码重复性大大提高。接下来继续分析菜单计价程序-2。
以下给出菜单计价程序-2的源代码及SourceMonitor的生成报表、类图以供参考:
1 import java.util.Scanner; 2 3 public class Main { 4 private static Scanner data = new Scanner(System.in); 5 6 public static void main(String[] args) { 7 8 Order order = new Order(); 9 10 Menu menu = new Menu(); 11 12 Record record=new Record(); 13 //将菜单储存 14 while (true) { 15 //输入菜名 16 String dishName = data.next(); 17 18 if (dishName.equals("end")) 19 break; 20 21 if(dishName.equals("delete")) 22 break; 23 if (!judge(dishName)) { 24 //输入单价 25 int price = data.nextInt(); 26 27 Dish d = new Dish(dishName, price); 28 29 menu.add(d,menu); 30 31 }//输入菜单 32 /**如果输入的dishName为纯数字,则为点单*/ 33 else if (judge(dishName)) { 34 35 int num;//序号 36 37 num = Integer.parseInt(dishName); 38 39 //菜名 40 String reDishName = data.next(); 41 42 //若reDishName为delete则删除序号num的菜单 43 if (reDishName.equals("delete")) { 44 45 order.deleteOrder(num,record); 46 47 continue; 48 } 49 /**菜单中不存在该菜*/ 50 51 //份额 52 int portion = data.nextInt(); 53 54 //份数 55 int n = data.nextInt(); 56 57 order.addARecord(num, reDishName, portion, n, menu); 58 59 //把订单存入数组列表中 60 } else if (dishName.equals("end")) 61 break; 62 } 63 64 65 //order.addARecord(dishName, portion,menu); 66 System.out.println((int) order.getTotalPrice()); 67 68 } 69 70 /** 71 * 判断输入的是否为数字 72 */ 73 public static boolean judge(String dishName) { 74 75 boolean result = dishName.matches("[0-9]+"); 76 77 if (result == true) { 78 return true; 79 } else { 80 return false; 81 } 82 } 83 84 } 85 86 //菜品类:对应菜谱上一道菜的信息。 87 88 class Dish { 89 90 String name;//菜品名称 91 92 int unit_price; //单价 93 94 //构造方法给成员变量赋值 95 public Dish(String name, int price) { 96 this.name = name; 97 this.unit_price = price; 98 } 99 100 //计算菜品价格的方法,输入参数是点菜的份额(输入数据只能是1/2/3,代表小/中/大份) 101 int getPrice(int portion, int n) { 102 float b[] = {1, 1.5f, 2}; 103 int p = (int) (unit_price * b[portion - 1] + 0.5) * n; 104 return p; 105 } 106 107 } 108 109 //菜谱类:对应菜谱,包含饭店提供的所有菜的信息。 110 111 class Menu { 112 113 public static Dish[] dishs;//菜品数组,保存所有菜品信息 114 115 public void add(Dish dish,Menu menu) { 116 int ln = 0; 117 118 //如果这是第一次输入该菜 119 if(searchDish(dish.name)==null) 120 { 121 if (dishs != null) 122 ln = dishs.length; 123 124 Dish[] tmp = new Dish[ln + 1]; 125 126 if (ln > 0) 127 System.arraycopy(dishs, 0, tmp, 0, ln); 128 129 tmp[ln] = dish; 130 131 dishs = tmp; 132 } 133 //否则,将后面输入的相同的菜替换之前的 134 //先找到相同的菜的下标,然后改变其单价 135 else { 136 int i=getNumOfDish(dish.name); 137 dishs[i].unit_price=dish.unit_price; 138 } 139 140 } 141 142 public int getNumOfDish(String dishName) 143 { 144 for (int j = 0; j< dishs.length; j++) { 145 if (dishs[j].name.equals(dishName)) 146 return j; 147 } 148 return -1;//无效 149 } 150 //根据菜名在菜谱中查找菜品信息,返回Dish对象。 151 public Dish searchDish(String dishName) { 152 153 if(dishs!=null) 154 { 155 for (int i = 0; i < dishs.length; i++) { 156 157 if (dishs[i].name.equals(dishName)) 158 return dishs[i]; 159 } 160 } 161 return null; 162 } 163 164 } 165 166 //点菜记录类:保存订单上的一道菜品记录 167 168 class Record { 169 170 int num;//序号 171 Dish d;//菜品 172 173 int portion;//份额(1/2/3代表小/中/大份) 174 175 int n;//份数 176 177 //构造方法为成员变量赋值 178 public Record() { 179 180 } 181 182 public Record(int num, Dish d, int portion, int n) { 183 this.num = num; 184 this.d = d; 185 this.portion = portion; 186 this.n = n; 187 } 188 189 int getPrice()//计价,计算本条记录的价格 190 { 191 return d.getPrice(portion, n); 192 } 193 194 195 } 196 197 //订单类:保存用户点的所有菜的信息。 198 199 class Order { 200 201 public static Record[] records;//保存订单上每一道的记录 202 203 Menu menu = new Menu(); 204 205 public static float getTotalPrice()//计算订单的总价 206 { 207 int total = 0; 208 209 if (records == null) { 210 return 0; 211 } 212 for (int i = 0; i < records.length; i++) { 213 total += (int) records[i].getPrice(); 214 215 } 216 return (int) total; 217 } 218 219 public Order() { 220 221 } 222 223 public Order(Menu menu) { 224 this.menu = menu; 225 } 226 227 Record searchOrderDish(int num) { 228 229 for (int i = 0; i < records.length; i++) { 230 if (records[i].num == num) 231 return records[i]; 232 } 233 return null; 234 } 235 Record addARecord(int num, String dishName, int portion, int n, Menu menu)//添加一条菜品信息到订单中。 236 { 237 //先查找是否有该道菜 238 Dish dish = menu.searchDish(dishName); 239 240 Record record = null; 241 242 if (dish != null) { 243 //将该道菜存入record中 244 record = new Record(num, dish, portion, n); 245 246 int ln = 0; 247 248 if (records != null) 249 ln = records.length; 250 251 Record tmp[] = new Record[ln + 1]; 252 if (ln > 0) 253 System.arraycopy(records, 0, tmp, 0, ln); 254 tmp[ln] = record; 255 records = tmp; 256 System.out.println(num + " " + dish.name + " " + (int) records[num - 1].getPrice()); 257 } else if (dish == null) { 258 System.out.println(dishName + " " + "does not exist"); 259 } 260 return record; 261 262 } 263 //main函数中已通过检查,在订单在存在该到菜时则开始deleteOrder 264 Record deleteOrder(int num,Record record) { 265 266 Record n=searchOrderDish(num); 267 268 if(n==null) 269 System.out.println("delete error;"); 270 else 271 { 272 if(records[num-1].n==0) 273 System.out.println("delete error;"); 274 else 275 records[num-1].n=0; 276 } 277 return record; 278 } 279 280 }
在菜单计价程序-2中,增加了订单删除功能,使题目难度增加,这时候我选择按照题目提供的模板:菜品类、菜谱类、点菜记录类、订单类四大类重构了我菜单计价程序-1的代码。在刚刚开始接触这么多类时,有时候会突然分不清不同类的功能,这时候java的命名方法的优势就凸显出来了,往往一看名字就能知道该类、该变量所代表的含义。
写菜单计价程序-2的时候,因对类的使用掌握不熟悉,犯了许多错误,我将在采坑心得中详细举例。比较菜单计价程序-1与菜单计价程序-2的题目难度与SourceMonitor的生成报表内容,可知虽然题目要求增加了,题目难度有一定的提高,但是代码的规范性大大提高,足以见类的使用给我们程序员带来的便捷,不仅在与编写的过程,在浏览他人的代码时,规范的代码更容易理解与修改。
(3)在题目集二中,还有一题也折腾我许久未能解决。就是“小明走格子问题”,在最开始的时候,我才用最容易也是最简单的方法,即套循环,变例所有情况。但是不出意料,代码的复杂程度很高,导致我修改了多次,依然只能拿到几分,甚至出现严重超时的情况。在网上查阅方法之后,学会了动态规划,降低了大量的重复计算。
(4)在题目集三中,对HashSet有了初步了解,其最大的特性就是最大的特点是存储的元素是没有重复的,而且是无序的。这在解决去除重复数据时是非常有效的。
以下给出去除重复数据这一题的相关代码:
1 import java.util.*; 2 public class Main { 3 public static void main(String[] args){ 4 Scanner data = new Scanner(System.in); 5 6 LinkedHashSet<Integer> hash = new LinkedHashSet<Integer>(); 7 8 int flag=0; 9 10 int n = data.nextInt(); 11 12 int i = 0; 13 14 while(i < n) 15 { 16 hash.add(data.nextInt()); 17 i++; 18 } 19 20 for (Integer s : hash) { 21 if(flag==0) 22 { 23 System.out.print(s); 24 flag = 1; 25 } 26 else System.out.print(" "+s); 27 } 28 29 } 30 }
在完成去除重复数据之后,接下来的题单词的统计与排序中,因一时审题不清晰,导致错误,后来发现重复的单词只能输出一次,这就巧妙的运用了以上去除重复数据的相关知识点,在将句子拆分之后,存入HashSet中将重复的单词去除,在按照题目所给出的规则(即按照每个单词的长度由高到低输出各个单词(重复单词只输出一次),如果单词长度相同,则按照单词的首字母顺序(不区分大小写,首字母相同的比较第二个字母,以此类推)升序输出),但是仍遇到一个问题,我将在采坑心得中详细分析。一下先给出该题的代码:
import java.util.Scanner; import java.util.*; public class Main { public static void main(String[] args){ Scanner data = new Scanner(System.in); String str = data.nextLine(); String [] a=str.split("\\s|\\.|\\, |\\. "); /**将文本排序*/ for(int i=0;i<a.length;i++) { for(int j=i+1;j<a.length;j++) { if(a[i].length()<a[j].length()) { String temp; temp=a[j]; a[j]=a[i]; a[i]=temp; } else if(a[i].length()==a[j].length()) { if(!judge(a[i],a[j])) { String temp; temp=a[j]; a[j]=a[i]; a[i]=temp; } } } } HashSet<String> hash = new LinkedHashSet<String>(); //将重复的元素删除: for(int i=0;i<a.length;i++) { hash.add((a[i])); } for (String s : hash) { if(s.length()!=0) System.out.println(s); } } static boolean judge(String A,String B){ String a= ToLowerCase(A); String b= ToLowerCase(B); //如果a需要先输出,则结果小于0 if(a.compareTo(b)<=0) return true; else return false; } public static String ToLowerCase(String str) { String b=""; for(int i=0;i<str.length();i++){ char temp=str.charAt(i); if(temp>='A'&&temp<='Z'){ temp=(char)(temp+32); } b+=temp; } return b; } }
除HashSet外,题目集-3还有关于面对对象编程中的封装问题(封装封装就是将属性私有化,提供公有的方法访问私有的属性)。在练习封装过程中,掌握了封装的基本用法与好处,如提高了程序的安全性,保护数据, 隐藏代码的实现细节, 统一接口, 增强系统的可维护性。
此外,题目集三中最困难的就是菜单计价程序-3,在该题中,增加了Table类,将订单信息细化为桌号标识、点菜记录和删除信息、代点菜信息,其中桌号信息包括桌号、时间。与时间有关的则是菜馆营业时间以及打折额度。此外,还新加一个代点菜功能,代点菜就是当前桌为另外一桌点菜,信息中的桌号是另一桌的桌号,带点菜的价格计算在当前这一桌。因其题目难度提升较大,该题分数不理想,没有及时完成,非常可惜。一下给出菜单计价程序-3的类图及流程图(由老师提供):
三、采坑心得
(1)菜单计价程序-1较为简单,未用类来解决,所以未遇到特别的难点。但在写菜单计价程序-2时,最大的难点就是对类的使用不熟练,对于构建实例对象概念模糊,在存入菜品时, 因Mune类不能熟练掌握,在最开始使用的时候,导致菜品不能成功加入菜谱中,后来在add函数中,加入了mune类的实例对象作为参数,解决了该问题。以下给出该题未通过的测试点及修改后的代码:
1 public void add(Dish dish,Menu menu) { 2 int ln = 0; 3 4 //如果这是第一次输入该菜 5 if(searchDish(dish.name)==null) 6 { 7 if (dishs != null) 8 ln = dishs.length; 9 10 Dish[] tmp = new Dish[ln + 1]; 11 12 if (ln > 0) 13 System.arraycopy(dishs, 0, tmp, 0, ln); 14 15 tmp[ln] = dish; 16 17 dishs = tmp; 18 } 19 //否则,将后面输入的相同的菜替换之前的 20 //先找到相同的菜的下标,然后改变其单价 21 else { 22 int i=getNumOfDish(dish.name); 23 dishs[i].unit_price=dish.unit_price; 24 } 25 26 }
我自己写的代码不能解决case17、18、22,这三类都属于异常菜名的删除问题,在对代码检查后,发现对于多条记录删除有提高的方法,以下给出我的删除代码:
Record deleteOrder(int num,Record record) { Record n=searchOrderDish(num); if(n==null) System.out.println("delete error;"); else { if(records[num-1].n==0) System.out.println("delete error;"); else records[num-1].n=0; } return record; }
(2)在第三次题目集中,单词统计与排序一题,在解决了单词重复问题后,仍为通过“较长文本”的测试点,在反复尝试,观察给出结果发现,HashSet允许有null值,所以在输出结果时,会多出为NUll,在对代码进行修改将hashSet改为LinkedHashSet后,顺利通过测试点。(详细代码见二、设计与分析-(4))
(3)在第三次题目集中,菜单计价程序-3难度上升,提高很大,在设计代码时遇到了一些困难,尤其是table类的设计,以下给出相关代码及关于table的类图:
1 class Table { 2 ArrayList<Record> records = new ArrayList<Record>(); 3 int num; 4 int phase;//时区,中午1,晚上2 7 LocalDateTime time; 8 int weekDay; 15 long sum = 0; 16 long origSum = 0; 17 ArrayList<Table> tables=new ArrayList<>(); 18 public LocalDateTime getTime() { 19 return time; 20 } 21 public void setTime(LocalDateTime time) { 22 this.time = time; 23 } 24 public int getNum() { 25 return num; 26 } 27 public void setNum(int num) { 28 this.num = num; 29 } 30 42 public Table() { 43 } 44 public void addRecord(Record record){ 45 records.add(record); 46 } 47 void setWeekDay() { 48 weekDay = time.getDayOfWeek().getValue(); 49 } 50 boolean inRange(){ 51 if(!(time.isAfter(LocalDateTime.of(2022, 1, 1, 0, 0, 0)) 52 &&time.isBefore(LocalDateTime.of(2024, 1, 1, 0, 0, 0)))) 53 { 54 return true; 55 } 56 return false; 57 } 58 Table isSamePhase(Table table){ 59 int flag=-1; 60 for (int i = 0; i < tables.size(); i++) { 61 // 有重复的桌号 62 if (table.num == tables.get(i).num && tables.get(i).isOpen()) { 63 Duration duration = Duration.between(table.time, tables.get(i).time); 64 // 同一天 65 if (duration.toDays() == 0) { 66 // 在周一到周五 67 if (table.weekDay > 0 && table.weekDay < 6) { 68 // 在同一时间段 69 if (table.phase==tables.get(i).phase&&table.phase!=0) { 70 flag=1; 71 table = tables.get(i); 72 return table; 73 } 74 } 75 // 在周末 76 else { 77 // 时间相差小于一小时 78 if (duration.toHours() < 3600) { 79 flag=1; 80 table = tables.get(i); 81 return table; 82 } 83 } 84 } 85 } 86 else if(table.num == tables.get(i).num && !tables.get(i).isOpen()){ 87 System.out.println("table "+tables.get(i).num+" out of opening hours"); 88 return table; 89 } 90 } 91 return table; 92 } 93 156 void getSum() { 157 for (Record record : records) { 158 if (!record.isDeleted) { 159 double price = record.getPrice(); 160 origSum += price; 161 if (record.dish.isT) { 162 if (weekDay > 0 && weekDay < 6) { 163 sum += Math.round(price * 0.7); 164 } else { 165 sum += price; 166 } 167 } else { 168 if (weekDay > 0 && weekDay < 6) { 169 int hour = time.getHour(); 170 int minute = time.getMinute(); 171 if ((hour >= 17 && hour < 20) || (hour == 20 && minute <= 30)) { 172 sum += Math.round(price * 0.8); 173 } else if ((hour >= 10 && hour < 14) || (hour == 14 && minute <= 30)) { 174 sum += Math.round(price * 0.6); 175 } else { 176 sum += price; 177 } 178 } else { 179 sum += price; 180 } 181 } 182 } 183 } 184 } 185 void setPhase(LocalDateTime time){ 186 int phase=0; 187 /**在中午*/ 188 if(time.isAfter(LocalDateTime.of(time.getYear(), time.getMonth(), time.getDayOfMonth(), 10, 30, 0)) 189 &&time.isBefore(LocalDateTime.of(time.getYear(), time.getMonth(), time.getDayOfMonth(), 14, 30, 0))) 190 { 191 phase=1; 192 } 193 /**在晚上*/ 194 if(time.isAfter(LocalDateTime.of(time.getYear(), time.getMonth(), time.getDayOfMonth(), 17, 0, 0)) 195 &&time.isBefore(LocalDateTime.of(time.getYear(), time.getMonth(), time.getDayOfMonth(), 20, 30, 0))) 196 { 197 phase= 2; 198 } 199 } 200 boolean isOpen() { 201 if (weekDay > 0 && weekDay < 6) { 202 if ((time.getHour() >= 17 && time.getHour() < 20) || (time.getHour() == 20 && time.getMinute() <= 30) || (time.getHour() > 10 && time.getHour() < 14) || (time.getHour() == 10 && time.getMinute() >= 30) || (time.getHour() == 14 && time.getMinute() <= 30)) 203 return true; 204 } else { 205 if ((time.getHour() > 9 && time.getHour() < 21) || (time.getHour() == 9 && time.getMinute() >= 30) || (time.getHour() == 21 && time.getMinute() <= 30)) 206 return true; 207 } 208 return false; 209 } 210 }
在对比老师的table时发现,其更加简洁,自己的则非常繁琐,但是对于刚刚接触类不久的我来说,完成这个已经是不小的进步了。
同时,菜单计价程序-2的菜单及订单的增删代码过于繁杂(详细代码见上面),以下附上修改后的代码,用Arraylist代替了传统数组,大大方便了代码的书写。
关于菜谱添加菜:
1 public void addDish(Dish dish){ 2 Dish temp=searchDish(dish.getName()); 3 if(temp==null) 4 dishes.add(dish); 5 else if(temp!=null){ 6 dishes.remove(temp); 7 dishes.add(dish); 8 } 9 }
删除订单:
boolean delARecordByOrderNum(int orderNum) { int i = 0, flag = 0; for (i = 0; i < records.size(); i++) { if (records.get(i).orderNum == orderNum) { if (records.get(i).isDeleted == false) { records.get(i).isDeleted = true; } else { System.out.println("deduplication " + orderNum); } flag++; } } if (flag == 0) { System.out.println("delete error;"); return false; } return true; } }
四、主要困难以及改进建议
(1)主要困难:
一是知识点的补充,在完成这三次题目集时,最让我感觉到的就是知识的匮乏,因刚刚接触java,虽然之前学了C语言。二者由相通之处,但是java是面对对象编程,由许多类需要你去了解、认识并初步掌握其用法,在写作业的同时,查阅相关资料是必不可少的,但是也是比较耗费时间的。
二是关于IDEA不同报错类型的认识,对编译错误类型的认识与了解能够帮助我们在编译或运行出现问题时及时找出问题的关键,方便我们修改。
三、在写菜单计价程序相关的题目是,我觉得最为困难的就是将那一堆输入分类,然后做出相应的反应添加菜,下订单,删除订单,点菜结束等。
(2)改进建议:
一、在写菜单计价程序相关的题目时,我将关于输入样例的语句分类全部套在循环,放在主方法中,导致主方法过于复杂,在经过老师上课对该题的讲解后,我意识到可以单独创建一个字符串解析类,顾名思义,这个类就是用来判断输入的字符串符合的类型,是否带菜、在菜谱中增加菜等。通过该类中的方法实现。这样能使代码更加简洁明了,及让代码更加符合规范。此外,尽可能的将方法简化,将重复代码提出,减少重复代码,提高代码的复用性。
二、有SouranceMonitor的生成报表图可知,对代码的注释需要增加,这在写代码中也是非常重要的一环,有简单明了的注释,在自己写代码的过程中也能够让自己读懂,方便自己前后增添、修改代码。除代码的注释外,对变量的命名也要规范,要使用简单易懂,具有意义的名称,使代码易于理解与维护。
五、总结
(1)这三次作业包含的知识点很广泛,极大的补充了我的知识库,让我对java这门语言有了更深入的了解,也让我学会了更好的自主学习,自我学习。
(2)对于写题而言,仔细审题是解决问题的关键,在动手开始写代码之前,一定要仔细审题,看清楚题目的要求,不要拿到题目就急着写代码,这样不仅不会更快,反而会增加错误,在通过测试点发现过不了时,才恍然大悟,错过了题目中关键的信息,在写菜单计价程序时,其题干非常的长,要求也一次次的变得复杂,我们要想在这一题上去的高分,一定要耐心审题,弄清楚、弄明白题目的要求,不要盲目的做题,等到过不了测试点才去看题目。
(3)改进:要多花一点时间在学习java上,java中的已经定义的类是非常多的,仅仅是写题目的时候用一次只能有一个粗略映像,需要花其他时间去巩固。此外,在写题目时,自己需要更有耐心一点,不要急于求成,也不要不带脑子写代码,使得写出的代码不仅得分不高,质量还低。
(4)对于老师布置的作业,我觉得老师布置的作业题量适中,精确把握在我们能够完成的时间内。