oo-pre第六次作业总结

题面

(这里只强调改动)

  1. qsend和qrecv的模糊查询模式新增四个参数-ssq, -ssr, -pre, -pos,从左到右分别表示:查询以后续输入字符串为子序列(subsquence),子串(substring),前缀(prefix),后缀(postfix)的发送者/接收者名称的消息。即现在上述两条指令的完整形式为qsend [-v] [param] “str”和qrecv [-v] [param] “str”。其中param为上述四个参数中的一个。当param缺省而-v存在时,表示含义与param=-ssr且-v存在时一致。
  2. 所有指令新增参数-c “str”(-c: clean)表示将所有该指令输出消息中的消息内容中的子串str(引号是指令格式要求,并非str内的内容)改为由字符’ * ‘组成的字符串后再输出,其长度与被修改的字符串长度相同。该参数永远是所有指令的最后一个参数。且子串匹配优先级为从左到右(具体解释见样例)
    指令参数之间的顺序关系见后续限制说明部分以及样例。

这里我们还需要大家实现对指令格式的检查。格式错误的指令只需按照指导书要求输出即可,无需将错误指令执行,具体如下:

  1. qdate指令的日期若不符合现实中的日期表示,则需要输出Command Error!: Wrong Date Format! “cmd”,其中cmd为出错指令。
  2. qsend和qrecv中,新增的四个参数只适用于模糊匹配,所以,若-ssq, -ssr, -pre, -pos参数在-v参数之前出现,或-v参数未出现而这四个参数出现了,则需要输出Command Error!: Not Vague Query! “cmd”,其中cmd为出错指令。
  3. 显然一条指令至多只有一种错误。

输入样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2023/12/23-timetraveler:"I am from future!!!";
2022/8/3-fishlifehh:"I am lying flat@kasumi . too tired to tap.";
2021/3/12-meloneater:"@timetraveler so what is happen, maybe I should say what will happen in the future?";
1999/12/31-earthwarrior:"so, do we win?"; 1999/12/31-militaryleader:"hey! @earthwarrior , you should abide by the agreement!"; 1999/12/31-earthwarrior@militaryleader :"aaah! I forget it.";
2022/3/23-ooer:"why you crashed again?";
2022/6/4-urgenter:"hurry! the next class will start immediately.";
0257/5/3-ancienter:"you mean this is the prophecy of a prophet.";
0257/5/4-ancientress@ancienter :"yes, and i dont know why we speak english.";
END_OF_MESSAGE
qdate 2022/2/30
qdate /13/
qsend -v -pos "er"
qrecv -pos "or"
qsend "militaryleader" -c "agreement"
qrecv -v -pre "an" -c "english"
qsend "earthwarrior" -c "aa"

输出样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
Command Error!: Wrong Date Format! "qdate 2022/2/30"
Command Error!: Wrong Date Format! "qdate /13/"
2023/12/23-timetraveler:"I am from future!!!";
2021/3/12-meloneater:"@timetraveler so what is happen, maybe I should say what will happen in the future?";
1999/12/31-militaryleader:"hey! @earthwarrior , you should abide by the agreement!";
2022/3/23-ooer:"why you crashed again?";
2022/6/4-urgenter:"hurry! the next class will start immediately.";
0257/5/3-ancienter:"you mean this is the prophecy of a prophet.";
Command Error!: Not Vague Query! "qrecv -pos "or""
1999/12/31-militaryleader:"hey! @earthwarrior , you should abide by the *********!";
0257/5/4-ancientress@ancienter :"yes, and i dont know why we speak *******.";
1999/12/31-earthwarrior:"so, do we win?";
1999/12/31-earthwarrior@militaryleader :"**ah! I forget it.";

样例解释:
对于最后一条指令,忽略-c参数时的输出如下:

1
2
1999/12/31-earthwarrior:"so, do we win?";
1999/12/31-earthwarrior@militaryleader:"aaah! I forget it.";
  1. 考虑-c第一条消息的内容不以aa为子串,而第二条消息的内容中存在子串aa,根据从左往右的匹配规则,将前两个aa替换成* *后输出。
  2. 若第二条消息的原版是1999/12/31-earthwarrior@militaryleader:”aaaah! I forget it.”;,则根据从左到右的匹配规则,会先匹配到前两个aa并将其替换成* ,然后匹配到后两个aa并替换成 ,最终输出的是1999/12/31-earthwarrior@militaryleader:” * * *h! I forget it.”;(星号中间没有空格)

限制说明(在上一次的基础上):

  1. 保证所有的消息符合格式,指令错误只涉及上面谈到的那些类型。
  2. qdate指令保证输入的日期中年月日三个参数不同时缺省。
  3. qsend和qrecv指令中,保证-ssq, -ssr, -pre, -pos四个参数至多出现其中一个。
  4. 正文内容仅由大小写英文字母、数字、空格、四种标点符号(? ! , .)构成。
  5. 日期格式异常只需考虑月,日大小以及平闰年,无需考虑1582年的10月份少了10天等类似的非常偏门的错误。
  6. 当日期发生参数缺省时,只需考虑已给定参数的合法性,具体而言。若在固定已有参数值的情况下,存在缺省参数的一组取值,使得日期合法,则认为该日期参数合法。
    例子1:指令qdate /2/29合法,因为2月可以有29日,且当年份为闰年时,整个日期合法,所以 该日期参数合法。
    例子2:指令qdate /9/31非法,因为9月不可能有31日,这与缺省的年份无关。
    例子3:指令qdate 2022//31合法,因为存在有31号的月份。
    例子4:指令qdate //32非法,因为不存在有32号的月份。这与缺省的年、月份无关。
    例子5:指令qdate /13/非法,因为不存在有13月的年份,这与缺省的年、日无关。
    例子6:指令qdate //0非法,因为不存在0号。

题解

(同样只重点提一下和上次的差别)

  1. 由于和之前的状态相比,对于输入的要求字符串要匹配的项目变多,如果全部放在main中会让main函数比较冗长,因此新建了一个operate类对这些要求进行分类读取。
    main函数中:

    1
    2
    3
    4
    5
    Operate operate = new Operate();
    while (scanner.hasNext()) {
    String inputQuest = scanner.nextLine();
    operate.operation(inputQuest, memory);
    }
  2. 对operate类,要进行两到三个东西的检查:如果是针对发送者和接收者,则要检查-v,param和-c的检查。
    注意几个事情:首先是对-c进行检查,如果没有-c就直接判断-v(因为本体param和-v是完全相关的,一个正确的模糊输入必须要有-v,所以只需要整个输入就不会漏掉信息),其次是如果有-c的情况,要单独分离-c后面的屏蔽词,所以不能直接split”-“符号(可能把-v也split了),我的方法是把它用另一个符号”#“替换再分开即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    if (quest.substring(0, 5).equals("qsend")) { //查询输入相同发送者
    if (!quest.contains("-c")) { //不需要屏蔽词
    if (quest.substring(6, 8).equals("-v")) { //模糊输入
    memory.printSenderV(quest.substring(6), quest, "");
    } else {
    memory.printSender(quest.substring(6), quest, "");
    }
    } else {
    String newQuest = quest.replace("-c", "#c");
    String[] clearString = newQuest.split("#");
    String[] needClear = clearString[1].split("\"");
    if (quest.substring(6, 8).equals("-v")) { //模糊输入
    memory.printSenderV(clearString[0].trim().substring(6), quest, needClear[1]);
    } else {
    memory.printSender(clearString[0].trim().substring(6), quest, needClear[1]);
    }
    }
    }

    而如果是针对时间则只需要考虑-c的情况,同样需要分离需要被替换的词(先用-分再用“分,最后取出)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (quest.substring(0, 5).equals("qdate")) { //查询输入相同日期
    if (!quest.contains("-c")) { //不需要屏蔽词
    memory.printDate(quest.substring(6), quest, "");
    } else { //需要屏蔽词,格式为qdate yyyy/mm/dd -c "Str"
    String[] clearString = quest.split("-");
    String[] needClear = clearString[1].split("\"");
    memory.printDate(clearString[0].trim().substring(6), quest, needClear[1]);
    } //qdate 12/23 -c "f"
    }

3.对于时间的输入,首先判断是否错误。我在这里写了一个判断错误的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public boolean dateCheck(int year, int month, int day) {
boolean runYear = false;
if (year != -1) { //年份不缺省,需要判断闰年
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
runYear = true;
}
}
if (year == -1) { //年份缺省,考虑最好情况
runYear = true;
}
//考虑各种不成立的情况
if (month != -1 && day != -1) { //无缺省或缺省年份
if (month > 12) {
return false;
} else if (month == 2) {
if (!runYear && day > 28) {
return false;
}
return !runYear || day <= 29;
} else if (month == 4 || month == 6 || month == 9 || month == 11) {
return day <= 30;
} else {
return day <= 31;
}
} else if (month != -1 && day == -1) {
return month <= 12;
} else if (month == -1 && day != -1) {
return day <= 31;
}
return true;
}

其精髓就是要考虑最好情况,就比如在年份缺省的时候,就默认其为闰年,默认2月可以有29天;如果月份缺省,就默认每个月最多都可以有31号。

然后首先进行错误分析,进行错误抛出:

1
2
3
if (!dateCheck(inputYear, inputMonth, inputDay)) { //非法情况
System.out.println("Command Error!: Wrong Date Format! \"" + cmd + "\"");
} else { //合法情况

对正确情况,就进行屏蔽操作。我对printDate,printSender等等多加了一个字符串的输入,代表的是应该被屏蔽的字符串。当其为空时则不用屏蔽,如果非空就进行屏蔽操作:

1
2
3
4
5
6
7
8
if (needClear.equals("")) { //不用屏蔽
System.out.print(strings.get(index).getUsedString().trim());
System.out.println(";");
} else { //需要屏蔽
String out = strings.get(index).getUsedString().trim();
System.out.print(changeOutput(needClear, out));
System.out.println(";");
}

对于屏蔽操作,我也进行了一个函数的封装,用来对原有字符串改变为星号。其原理就是,首先看屏蔽词的长度,然后生成同样长度的星号串,最后利用replace函数将内容改变。
这里有一个重点:屏蔽词只屏蔽内容!比如屏蔽字符”1“就只能屏蔽内容的1而不能屏蔽日期中的。所以要把原有字符串引号中的内容提取出来再进行更换,最后字符串拼接后输出。

1
2
3
4
5
6
7
8
9
10
public String changeOutput(String clear, String beforeOutput) {
String[] words = beforeOutput.split("\"");
int length = clear.length();
String change = "";
for (int i = 0; i < length; i++) {
change = change + "*";
}
String afterOutput = beforeOutput;
return words[0] + "\"" + words[1].replace(clear, change) + "\"";
}
  1. 考虑发送、接收者相关函数:
    如果没有-v时,判断错误只需要判断有没有多余的-param就可以,此时同样写了一个函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public boolean checkPeople(String quest) {
    if (quest.contains("-ssq")) {
    return false;
    }
    if (quest.contains("-ssr")) {
    return false;
    }
    if (quest.contains("-pre")) {
    return false;
    }
    if (quest.contains("-pos")) {
    return false;
    }
    return true;
    }
    这样判断完这个函数就可以直接利用上面的方法进行输出,不再赘述。

如果有-v时,也需要判断错误,但是这个时候判断的错误是-param是否在-v前面,这个函数需要改成:

1
2
3
4
5
public boolean checkPeopleV(String quest) {
if (quest.indexOf("-ssq") < quest.indexOf("-v") && quest.contains("-ssq")) {
return false;
}
//……

意思就是如果有param就要有后置的一个-v,非常易于理解。随后就是针对不同的四种param进行输出处理。
对于-ssq:此处检测的是子序列,没有现成的函数,需要自己写一个函数进行判断,这里是-ssq的输出函数:(同样最后考虑了是否需要屏蔽,同样套用上面的方法)

1
2
3
4
5
6
7
8
9
10
11
12
if (cmd.contains("-ssq")) {
if (checkSsq(name[1], strings.get(index).getReceiverName())) {
if (needClear.equals("")) {
System.out.print(strings.get(index).getUsedString().trim());
System.out.println(";");
} else {
String out = strings.get(index).getUsedString().trim();
System.out.print(changeOutput(needClear, out));
System.out.println(";");
}
}
}

子序列检测函数:(子序列相当于可以断开的子串)就是对子串的每一个字符都从检测字符串从前往后检测,如果检测到了就从该位开始检测下一个字符,最后如果所有字串字符都能按顺序在检测字符串中找到,则证明子串是子序列。函数如图:

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean checkSsq(String quest, String string) {
String questIn = quest;
String stringIn = string;
while (stringIn.length() != 0 && questIn.length() != 0) {
if (stringIn.charAt(0) == questIn.charAt(0)) {
questIn = questIn.substring(1);
stringIn = stringIn.substring(1);
} else {
stringIn = stringIn.substring(1);
}
}
return questIn.length() == 0;
}

对于-pre(前缀检测):直接利用现有的前缀检测函数startsWith即可。如下:

1
2
3
else if (cmd.contains("-pre")) {
if (strings.get(index).getSenderName().startsWith(name[1])) {
//……

对于-pos(后缀检测)也类似,只是利用的是后缀检测函数endsWith。
对于-ssr,就和只有-v是等价的,只需要进行屏蔽操作就可以了,不再赘述。

以上,就是第六次作业的改动总结。又一个场景结束啦,最后一次看上去好难的样子orz