OO_pre部分精品贴
Part 1
自动化测试
省流不看版
基于递归下降和形式化文法的随机数据生成 + 基于shell脚本的简易评测机。
一、基于递归下降和形式化文法的随机数据生成器
结合题目的形式化描述,我们可以容易得到以下文法:
符号[…]表示方括号内包含的为可选项
符号{…}表示花括号内包含的为可重复 0 次或多次的项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 消息 Message -> PersonMessage | GroupMessage 个人消息 PersonMessage -> Date '-' UserName '@' UserName ' ' ':' '"' MessageContent '"' ';' 日期 Date -> Year'/' Month'/' Day 年 Year -> 1 -9999 月 Mon -> 0 -99 日 Day -> 0 -99 用户名 UserName -> {大小写英文字母 | 数字} 消息内容 MessageCount -> { 大小写英文字母 | 数字 | 空格 | '?' | '!' | ',' | '.' } 群聊消息 GroupMessage -> Date '-' UserName ':' '"' MessageContent ['@' UserName ' ' ] '"' ';' 指令 order -> qsend | qrecv | qdate 查询发送者 qsend -> 'qsend ' ['-v' ] [param] '"' SenderName '"' ['-c' '"' cOrderContent '"' ] 查询接收者 qrecv -> 'qrecv ' ['-v' ] [param] '"' RecvName '"' ['-c' '"' cOrderContent '"' ] 发送者名 SenderName -> {大小写英文字母 | 数字} 接收者名 RecvName -> {大小写英文字母 | 数字} c参数后字符串 cOrderContent -> { 大小写英文字母 | 数字 | 空格 | '?' | '!' | ',' | '.' |'@' } 参数 param -> '-ssq' | '-ssr' | '-pre' | '-pos' 查询日期 qdate -> 'qdate ' Date
基于此文法就可以利用递归下降法 生成符合题目要求的随机数据,需要注意的特殊条件有以下几个:
在 GroupMessage
中 @ userName
只出现一次
'-v'
和 param
的出现顺序可以调换以模拟非法指令
具体以生成 PersonMessage
的过程为例:
1 在Message
函数中以 50%的概率返回 personMessage
:
1 2 3 4 5 6 7 8 public static String Message () { int rate = random.nextInt(2 ); if (rate == 1 ) { return personMessage(); } else { return groupMessage(); } }
2 在personMessage
函数中以文法格式返回正确的 personMessage
,其中 randomInt
函数生成一个[10,50)的正整数, Date
函数返回正确的日期, UserName
函数会返回程序最开始生成的 用户名池 中的一个用户名(这样做可以增加用户名重复的概率,提高测试强度),MessageContent
函数下文讲解。
1 2 3 4 5 6 7 8 9 public static String personMessage () { int messageLength = randomInt(10 , 50 ); return Date() + '-' + UserName() + '@' + UserName() + ' ' + ':' + '"' + (messageLength, false ) + '"' + ';' ; } public static int randomInt (int min, int max) { return random.nextInt(max) % (max - min + 1 ) + min; }
3 MessageContent
函数返回消息内容,length
参数决定生成的消息长度,第二个参数可以决定消息内容中是否会有概率出现一次 @ userName
:
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 32 public static String MessageContent (int length, Boolean type) { StringBuilder message = new StringBuilder (); String userName = "" ; String special = " !?.," ; for (int i = 0 ; i < length; i++) { int chatType = random.nextInt(4 ); switch (chatType){ case 0 : message.append(random.nextInt(10 )); break ; case 1 : message.append((char ) (random.nextInt(26 ) + 97 )); break ; case 2 : message.append((char ) (random.nextInt(26 ) + 65 )); break ; case 3 : int rate = random.nextInt(5 ); message.append(special.charAt(rate)); break ; } } if (type) { userName = '@' + UserName() + ' ' ; int indexOfUserName = random.nextInt(length); String output = "" ; output += message.substring(0 , indexOfUserName) + userName + message.substring(indexOfUserName); return output; } else { return message.toString(); } }
至此我们就成功生成了一条 PersonMessage
,其他文法成分同理
二、导出 jar
包
为了方便后续使用脚本进行自动评测,我们需要和小伙伴各自导出自己程序的 jar
包(相当于C语言编译出的可执行文件 .exe),方法非常简单,我只做简单概述,遇到困难可以私信助教 :
1 文件->项目结构
2 工件->添加
3 jar->具有依赖项的模块
4 点击索引文件,选择程序入口文件(主类),我是 MainClass
5 一路确定+应用,最后在导航栏找到构建,选择构建工件
6 选择你的jar构建即可在 与 src
同级的 out
文件夹下的 artifacts
文件夹 下找到jar
包
三、基于 shell
脚本的自动评测机
这部分利用上面生成好的数据和导出的jar实现一个自动评测机,需要一定的 shell
编程基础,但其实不是特别难。
用法:
组织文件结构如下:
其中 test
文件夹下存放测试数据,如testcase1.txt
,而cjj.jar/ccy.jar
是对拍程序的jar包,auto.sh
就是执行脚本。
需要选一个基准对拍程序,比如我这里选择了 cjj.jar
,那么脚本就会出现这样的语句
1 java -jar cjj.jar < input.txt > cjj.txt
之后我们在 names
变量中添加其他人的jar
包名即可
脚本如下:
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 32 #!/bin/bash names=("ccy" ) for dir in $(ls ./) do if [ -d $dir ]; then for testcase in $(ls ./$dir ) do cp $dir /$testcase input.txt echo "$dir /$testcase " "begin" echo "======= " cjj " ======" java -jar cjj.jar < input.txt > cjj.txt echo for name in ${names[*]} do echo "======= " $name " ======" java -jar ${name} .jar < input.txt > ${name} .txt echo diff ${name} .txt cjj.txt > difference.txt if [ $? -ne 0 ] ; then echo ${name} "wrong at" "$dir /$testcase " exit 1 fi done echo "====================" echo "$dir /$testcase " "end" echo echo done fi done
Part 2
第六次作业指南
本次作业总体新增三大需求,下面对这三大需求分别分析并实现
需求1:处理指令异常问题
qsend/qrecv
这两条指令的异常情况相同,故可将二者归为一类,有如下两种可能:
不出现-v, 但出现-ssq/-ssr/-pre/-pos
出现-v且出现-ssq/-ssr/-pre/-pos,但是后者在前者之前
该判断较为简单,只需通过正则表达式分别对-v以及-ssq/-ssr/-pre/-pos进行匹配并获得二者出现的index(如果有的话)并进行比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static boolean isRightCmdUser (String content) { int indexFour; int indexV; Pattern pattern1 = Pattern.compile("-ssq|-ssr|-pre|-pos" ); Pattern pattern2 = Pattern.compile("-v" ); Matcher matcher = pattern1.matcher(content); if (matcher.find()) { indexFour = matcher.start(); matcher = pattern2.matcher(content); if (matcher.find()) { indexV = matcher.start(); if (indexFour < indexV) { System.out.println("Command Error!: Not Vague Query! \"" + content + "\"" ); return false ; } } else { System.out.println("Command Error!: Not Vague Query! \"" + content + "\"" ); return false ; } } return true ; }
qdate
该指令出现异常的可能情况比较繁杂,分类讨论的思路较多,但是每一个if-else
都保证遵循严谨的逻辑关系,则该判断并没有太大的难度。下面只给出一些值得注意的点
1 2 3 4 1. day 与 month 的输入值可能是0,注意在`if(day <= 31/30/...)` 的同时不要忘记条件`day > 0 ` 2. 什么是闰年: 满足如下条件之一,即为闰年(int year = Integer.parseInt(yearString);) 1. year % 4 == 0 && year % 100 != 0 2. year % 400 == 0
需求2:判断某个消息是否符合指令要求
qsend/qrecv
为了判断某则消息是否符合指令要求,我们首先需要知道同消息sender/receiver name的匹配原则(严格匹配,前缀匹配等等),在此我们可以定义此需求下的第一个方法。
Step1 : 判断匹配模式的Method —— judgeMatchType(String)
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 public static int judgeMatchType (String instruction) { int findModel; Pattern pattern = Pattern.compile("-v" ); Matcher matcher = pattern.matcher(temp); if (matcher.find()) { findModel = 1 ; pattern = Pattern.compile("-ssq|-ssr|-pre|-pos" ); matcher = pattern.matcher(temp); if (matcher.find()) { switch (matcher.group()) { case "-ssq" : findModel = 2 ; break ; case "-pre" : findModel = 3 ; break ; case "-pos" : findModel = 4 ; break ; default : } } } else { findModel = 0 ; } }
在判断过匹配模式后,我们需要对具体的匹配的模式进行实现。
Step2 : 判断字符串small是否与big匹配
是否匹配要针对不同的匹配模式来考虑。考虑到不同模式之间的重合性(small可能既是big的子串,又是big的前缀),我们可以定义多个方法来分别对每个模式进行判断(而不是只定义一个方法)来使得思路更加清晰。
findModel == 0 (严格匹配)
该匹配模式看small和big是否相同,直接使用small.equal(big)来判断即可,可以不定义方法
findModel == 1(子串匹配)
1 2 3 4 5 6 7 public static boolean isSonSerial (String big, String small) { Pattern pattern = Pattern.compile(small.replace("?" ,"[?]" )); Matcher matcher = pattern.matcher(big); return matcher.find(); }
findModel == 2(子序列匹配)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static boolean isSonLine (String big, String small) { char [] charBig = big.toCharArray(); char [] charSmall = small.toCharArray(); int pointSmall = 0 ; for (char c : charBig) { if (c == charSmall[pointSmall]) { pointSmall++; if (pointSmall == small.length()) { return true ; } } } return false ; }
findModel == 3(前缀匹配)
1 2 3 4 5 6 7 public static boolean isPre (String big, String small) { Pattern pattern = Pattern.compile(small.replace("?" ,"[?]" )); Matcher matcher = pattern.matcher(big); return matcher.find() && matcher.start() == 0 ; }
findModel == 4(后缀匹配)
1 2 3 4 5 6 7 8 9 10 11 12 13 public static boolean isPos (String big, String small) { Pattern pattern = Pattern.compile(small.replace("?" ,"[?]" )); Matcher matcher = pattern.matcher(big); while (matcher.find()) { if (matcher.end() == big.length()) { return true ; } } return false ; }
qdate
对于qdate指令,在qdate指令合法的前提下本次作业的判断方法同上次作业判断,便不再赘述
需求3:实现关键字和谐功能
关键字和谐的功能在最后一步:输出正确答案时进行。这是三条指令所共有的需求,因此我们可以将其封装为一个方法供三条指令调用
1 2 3 4 5 6 7 public static void printAnswer (String instruction, String messageContent) {}
下面介绍该方法的具体实现,主要分为如下两个step
Step1 : 判断是否需要屏蔽(并获得屏蔽语句)
(通过String的一些方法完全可以实现相关功能,此处介绍正则版本)
1 2 3 4 5 6 7 8 9 Pattern pattern = Pattern.compile("-c +\"([@a-zA-Z0-9 ?,.!]+)\"" ); Matcher matcher = pattern.matcher(messageContent);if (matcher.find()) { } else { }
Step2 : 实现关键字屏蔽功能
(除下述方法外,群中大佬也提到了通过StringBuffer类来实现此功能等,此处只介绍一种思路) 直接给出如下代码,通过注释来展现思路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 char [] tempContent = messageContent.toCharArray();int sentenceBegin = content.indexOf("\"" ); Pattern pattern = Pattern.compile(sonString.replace("?" ,"[?]" )); Matcher matcher = pattern.matcher(messageContent); while (matcher.find()) { if (matcher.start() >= sentenceBegin + 1 ) { for (int i = matcher.start(); i <= matcher.end() - 1 ; ++i) { tempContent[i] = '*' ; } } } System.out.println(tempContent);
附加说明:正则表达式转义问题
关于异常PatternSyntaxException
报此异常大概是因为正则表达式的书写有了问题。
在本题中有可能是因为在使用Pattern.compile(string) (string是一个String量)时,string内部有 问号
,而问号
在正则表达式中是有特殊含义的(类似于关键字
),所以我们需要对string中的问号进行转义,具体方法为: 将问号
转换为[问号]
即可,为此我们可以使用String类的方法replace(String string1,String string2)(该方法将调用者的string1子串全部替换为string2字符串)
来将string中所有的问号
替换成[问号]
(在上文代码中已经体现),具体例子如下:
1 2 Pattern pattern = Pattern.compile(string.replace("?" ,"[?]" ));