oo-pre第四次作业总结

题面

小明发现在此聊天软件的群聊功能中,如果不是发给某个特定的人的消息,群聊消息中不会注明消息的接收者。
因为很多用户希望在群聊中让某位用户特别注意到某消息,所以他们喜欢在群聊中发送@某人的信息,这些信息会被系统识别为群聊中发送给相应用户的消息,会提醒被所@的人注意到。
形式化来说,消息可能被表现成以下三种模式:

  • 1.给个人的消息
    形式:yyyy/MM/dd-{sender}@{receiver }:”{message_content}”;
    例子:2021/07/12-student@teacher :”can i pass the exam?”; (指定了接收者teacher)

  • 2.群聊中的消息且指定接收者
    形式:yyyy/MM/dd-{sender}:”{message_content}”;(message_content内包含@{receiver })
    例子:2021/07/12-student:”can i pass the exam?@teacher “;

  • 3.群聊中的信息且未指定接收者
    形式:yyyy/MM/dd-{sender}:”{message_content}”;
    例子:2021/07/12-student:”can i pass the exam?”;

  • 输入格式:
    前若干行为消息内容,以一行END_OF_MESSAGE结尾。其中一行内可能有多条消息,每条消息之间和每行末尾可能存在若干空白字符(空格和制表符\t)。
    其后为多条询问,所有可能出现的询问格式如下:
    qdate year/month/day : 查询某日期的消息
    qsend “username” : 查询某用户名发送的消息
    qrecv “username” : 查询某用户名接收的消息。
    请注意,所有“群聊中@某用户的消息”均算作该用户接收的消息,即接受的消息包括:私聊该用户的消息和群聊中@某用户的消息。

  • 输出格式:
    对于每一条询问,输出指定消息(输入数据中可能存在多条消息符合条件,此时按照原顺序、原格式输出全部符合条件的消息)。
    输出中每条消息均单独占据一行。

  • 注意事项:

  1. 一行输入中可能包含多条消息,但一条消息只会完整地出现在一行内。
  2. 每条消息之间和每行末尾可能存在若干空白字符作为分隔(空格和制表符\t),也可能不存在。
  3. 保证所有的消息和指令符合格式。
  4. 保证输入的日期、用户名、正文都非空。
  5. 日期仅以 year/month/day 形式给出,year∈[0,9999],month∈[1,12],day∈[0,31]。日期中可能存在前导0,比如1月可以表示为01月,258年可以表示为0258年。且保证合法,以及包括前导0在内,年份的位数不超过4位,月、日的位数不超过两位。
  6. 发送者和接收者的用户名仅由大小写英文字母、数字组成。
  7. 正文内容仅由大小写英文字母、数字、空格、四种标点符号(? ! , .)构成。
  8. 输入数据中所有内容均对大小写敏感。
  9. 如果一条消息中存在@用户的情况(对应前两种消息模式),则保证该信息中@+用户名结构后面一定有一个空格,而且@用户最多只会在一个消息中出现一次。
  10. 不超过300行。
  11. 每行不超过10个消息。
  12. 总询问数不超过100条。

思路:

本题主要是考察正则表达式的使用,因此主题思路就是根据这三种信息分别生成三个对应的正则表达式进行匹配,然后利用捕获组分别获取一个字符串中日期,发送者和接收者的信息,和这个字符串封装到一起,最后构建此类的容器进行查找输出即可。

  • 代码结构:
    Main类中进行输入输出,构建Input类进行字符串操作,将每一条消息和其日期,发送者和接收者都封装入其中,最后再构建Memory类,其中创建一个Input容器来将所有消息储存。

  • 正则表达式的构建:
    针对三种信息分别创建了三条正则表达式:

    1
    2
    3
    4
    5
    6
    private static String packPattern1 = "\\s*(\\d+/\\d+/\\d+)-(\\w+)@(\\w+) :\"(.*)\"";
    //给个人的消息:yyyy/MM/dd-{sender}@{receiver }:"{message_content}";
    private static String packPattern2 = "\\s*(\\d+/\\d+/\\d+)-(\\w+):\"(.*(@(\\w+)\\s).*)\"";
    //群聊中的消息,指定了接收者:yyyy/MM/dd-{sender}:"{message_content}";,其中message_content内包含@{receiver }
    private static String packPattern3 = "\\s*(\\d+/\\d+/\\d+)-(\\w+):\"(.*)\"";
    //群聊中的消息,未指定接收者:yyyy/MM/dd-{sender}:"{message_content}";

    其中有几个需要注意的点:
    首先,由于本题一行中可能拥有多条消息且每条消息前可能有空格,因此需要用\\s*来对前空格进行匹配。
    其次,由于本题需要对一行读入多个消息的情况进行处理,使用的是java中的split方法,并且以分号“;”作为分割标志,依次方法分割则会让消息最后的分号消失,此时不能在正则表达式后添加分号,同时正则表达式后也可以保证无后空格。
    然后,由于需要提取日期,发送者和接收者信息,因此需要利用括号将需提取子串进行标记,才能利用捕获组进行捕获。
    最后,”.”匹配的是单个字符而非字符串,因此本题中想要匹配发送消息中的字符需要使用.*来进行匹配。
    (本题其实不需要捕捉双引号内的发送信息内容,所以不需要利用括号括起,我纯纯读题读错了)

  • 输入字符串的处理和匹配:
    处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void addString(String input) { //整体处理一行字符串
    String[] records = input.split(";"); //将字符串以分号分割
    for (String record : records) {
    if (record.trim().length() != 0) {
    Input newString = new Input(record);
    newString.match(); //对字符串进行匹配
    this.strings.add(newString);
    }
    }
    }

    此处的含义是,首先对每一行的字符串利用split在分号处断开,然后构建一个字符串数组将断开的段分别放入records数组中方便一个一个处理。由于害怕可能出现全是空格的空串干扰,所以利用了trim函数消除字符串的前后空格,若还有内容则放入一个新建的input类进行处理匹配封装即可,最后再放入为这些已经处理好的消息准备的存储容器strings中

匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void match() { //进行判断
Matcher matcher1 = pattern1.matcher(this.usedString);//检测本身的字符串
Matcher matcher2 = pattern2.matcher(this.usedString);//检测本身的字符串
Matcher matcher3 = pattern3.matcher(this.usedString);//检测本身的字符串

if (matcher1.find()) { //分组匹配输入
this.setTime(matcher1.group(1));
this.setSenderName(matcher1.group(2));
this.setReceiverName(matcher1.group(3));
this.setContent(matcher1.group(4));
} else if (matcher2.find()) {
this.setTime(matcher2.group(1));
this.setSenderName(matcher2.group(2));
this.setReceiverName(matcher2.group(5));
this.setContent(matcher2.group(3));
} else if (matcher3.find()) {
this.setTime(matcher3.group(1));
this.setSenderName(matcher3.group(2));
this.setReceiverName("NULL"); //群发没有单独接收者
this.setContent(matcher3.group(3));
}
}

分别创建三个matcher类用来匹配,然后利用find函数,如果函数返回值为真,证明消息和正则表达式匹配成功,此时再利用捕获组group函数对信息进行分别提取(group后的数字代表着要捕获正则表达式第几个左括号后匹配的信息,如果group(0)就是整个字符串),注意,只有先试用了find函数进行判断才能使用group函数进行捕获。

  • 对询问格式的确定和读入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    while (scanner.hasNext()) {
    String inputQuest = scanner.nextLine();
    if (inputQuest.substring(0, 5).equals("qdate")) { //查询输入相同日期
    memory.printDate(inputQuest.substring(6));
    }
    if (inputQuest.substring(0, 5).equals("qsend")) { //查询输入相同发送者
    memory.printSender(inputQuest.substring(6));
    }
    if (inputQuest.substring(0, 5).equals("qrecv")) { //查询输入相同接收者
    memory.printReceiver(inputQuest.substring(6));
    }
    }

    注意两点:
    第一,由于本体的询问次数没有要求,因此如果不对输入进行判断就无法停止,这是需要用到scanner类中的hasNext函数,有点类似C中的!=EOF,用来判断输入是否结束,如果没有结束就继续操作。
    第二,由于三个询问格式都很固定,前五个字母分辨了不同的操作,因此可以提取前面五个字母用来分辨,后面的字符串用来判断输出什么消息(例如某一天的消息)。想要提取子串利用substring函数并指定开始和结束范围就可以。

  • 输出某日期消息:
    这是比较特殊的一个输出,因为按照题意,有可能存在消息中的时间是123/10/16而询问中的时间是0123/10/16。而此时也需要匹配,同时我们不能改变消息本身,因为之后还要原样输出,因此可以将年月日分割后分别化成int型进行比较即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void printDate(String inputDate) {
    String[] inputTimes = inputDate.split("/");
    int inputYear = Integer.parseInt(inputTimes[0]);
    int inputMonth = Integer.parseInt(inputTimes[1]);
    int inputDay = Integer.parseInt(inputTimes[2]);
    for (int index = 0; index < strings.size(); index++) {
    String[] times = strings.get(index).getTime().split("/");
    int year = Integer.parseInt(times[0]);
    int month = Integer.parseInt(times[1]);
    int day = Integer.parseInt(times[2]);
    if ((inputYear == year) && (inputMonth == month) && (inputDay == day)) {
    System.out.print(strings.get(index).getUsedString().trim());
    System.out.println(";");
    }
    }
    } //打印日期

    思路就是利用split对消息和询问中的时间分割成年月日,然后再用Integer类中的parseInt类型,将字符串转化为整形进行比较即可。
    最后输出的时候,由于分割消息时会让分号消失,需要将分号补充。

  • 输出发送者,接收者:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void printSender(String inputSender) {
    String[] name = inputSender.split("\"");
    for (int index = 0; index < strings.size(); index++) {
    if (name[1].equals(strings.get(index).getSenderName())) {
    System.out.print(strings.get(index).getUsedString().trim());
    System.out.println(";");
    }
    }
    }

    (接收者的输出方式几乎完全相同)
    唯一需要注意的就是字符串之间的比较不能用==,而应该用equals函数。

这就是第四次作业啦,考完P0没过还是有点难受的,但是还是看开点,放平心态,自己努力复习就好,加油加油!