第一次个人编程作业
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 300 | 120 |
Development | 开发 | 800 | 800 |
· Analysis | · 需求分析 (包括学习新技术) | 120 | 180 |
· Design Spec | · 生成设计文档 | 10 | 10 |
· Design Review | · 设计复审 | 60 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 | 15 |
· Design | · 具体设计 | 30 | 40 |
· Coding | · 具体编码 | 600 | 750 |
· Code Review | · 代码复审 | 30 | 15 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 180 |
Reporting | 报告 | 60 | 120 |
· Test Repor | · 测试报告 | 10 | 20 |
· Size Measurement | · 计算工作量 | 10 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
· 合计 | 2155 | 2360 |
二、计算模块接口
2.1计算模块接口的设计与实现过程
2.1.1概述
拿到题目以后查阅了很多资料,大多是使用DFA算法或者是AC自动机算法,考虑到测试的文件内容可能会很多因此选择了效率更高的DFA算法实现,然而百度的DFA算法都只是最基本的,远远无法实现这个项目的需求,因此还需要进行扩展,利用了汉字转拼音、汉字繁体转简体的第三方库。
2.1.2项目设计结构
设计了6个类和一个接口,包括主启动类、IO工具类、敏感词库类、DFA操作类等。首先这个项目需要读入文件,因此首先封装的就是IO工具类,其中主要三个方法就是读入敏感词、读入文章和输出答案以供启动类调用。在读入敏感词文件时调用敏感词库类中的方法构建敏感词库,读入文章按行遍历进行DFA操作过滤敏感词最后再用IO工具类输出结果。
2.1.3思路设计
对于汉字、拼音、部首等问题,从构建敏感词库入手,先将各个汉字进行拆分、转拼音等操作,然后进行类似排列组合的操作构建一个敏感词库,以拆分部首为例,像“你好”,可以构建为“亻尔女子”等4种情况(每个位置可以是原来字也可以是拆分后的字,2^2=4)。构建完敏感词库后进行DFA操作。对于谐音梗就在检测时转为拼音,用自己写的MyString类来保证输出时是文章原来内容。
2.1.4关键算法DFA
过滤方式就是基本的DFA,过滤之前用正则表达式做了一些处理比如去除特殊符号,转换为小写,繁体转简体,在filter1函数中调用的parse函数返回的是Mystring列表,目的在于汉字转拼音后能够保存汉字在原来文本中的位置以便最后输出答案
public void filter1(String word,int line) {
//去除特殊符号,转换为小写,繁体转简体
String str = word.replaceAll("([^\\u4e00-\\u9fc2A-Za-z0-9])", " ");
str = str.toLowerCase();
String simple = ZhConverterUtil.toSimple(str);
String s = "";
List<MyString> myStrings = parse(simple);
for (MyString myString : myStrings) {
s += myString.string;
}
//循环进行DFA检测
for (int i = 0; i < s.length(); i++) {
this.sensitive = "";
if (s.charAt(i) == ' ') {
continue;
}
int check = check(s, i);
if (check == -1) {
continue;
}
int begin=i;
int end=check;
for (int j = 0; j < myStrings.size(); j++) {
if (i > myStrings.get(j).getLength()) {
begin = myStrings.get(j+1).getIndex();
} else if (i == myStrings.get(j).getLength()) {
begin = myStrings.get(j+1).getIndex();
}
if (check > myStrings.get(j).getLength()) {
end = myStrings.get(j+1).getIndex();
} else if (check == myStrings.get(j).getLength()) {
end = myStrings.get(j).getIndex();
}
}
IOUtils.addAns(this.sensitive, line, word.substring(begin, ++end));
}
}
/**
* 检查文字中是否包含敏感字符
*
* @param words 文本
* @param beginIndex 开始位置
* @return 如果存在,则返回敏感词字符的长度,不存在返回-1
*/
private int check(String words, int beginIndex) {
char beginChar = words.charAt(beginIndex);
if (beginChar == ' ')
return -1;
boolean flag = false;
Map nowMap = wordsMap;
int i;
for (i = beginIndex; i < words.length(); i++) {
char word = words.charAt(i);
//最长匹配
if (word == ' ' && flag == true) {
break;
}
if (word == ' ' || beginChar >= 'a' && beginChar <= 'z' && word >= '0' && word <= '9' ) {
continue;
}
nowMap = (Map) nowMap.get(word);
if (nowMap == null) { //该字符不在敏感词库中直接退出循环
break;
}
this.sensitive += word; //是敏感词的字符,先进行拼接
flag = Boolean.TRUE.equals(nowMap.get(END_MARK_KEY));
}
return flag ? i : -1;
}
2.2计算模块接口部分的性能改进
由性能分析图可以看出,耗时间最长的两个方法都是第三方库的,汉字转拼音和繁体转简体的第三方库占用了大量了时间,然而我发现网上大部分的这种第三方库都是大同小异,好用是好用但是同样都很耗时间,我也没那个水平写一个比第三方库还牛逼的汉字转拼音、繁体转简体的工具,因此想优化也不太可能。至于我自己写的parse函数,因为这个函数比较简单,只是循环往ArrayList里添加元素,因此我也没想到更好的优化手段。耗时较多不知道是不是因为调用了汉字转拼音函数
public List<MyString> parse(String str) {
List<MyString> myStringList = new ArrayList<>();
for (int i = 0; i < str.length(); i++) {
String string = "";
MyString myString = new MyString();
char c = str.charAt(i);
if (isChineseChar(c)) {
String s = ""+c;
String toPinyin = PinyinHelper.toPinyin(s, PinyinStyleEnum.NORMAL);
myString.setString(toPinyin);
myString.setIndex(i);
} else {
myString.setString(String.valueOf(c));
myString.setIndex(i);
}
if (i == 0) {
myString.setLength(myString.string.length());
myStringList.add(i,myString);
} else {
myString.setLength(myStringList.get(i-1).length + myString.string.length());
myStringList.add(i,myString);
}
}
return myStringList;
}
2.3计算模块部分单元测试展示
init()函数测试,测试是否能够构建目标敏感词库
测试覆盖率
filter1()函数测试,测试是否能够正确过滤敏感词并按正确格式输出
测试覆盖率
完整运行给出的样例所得结果:
怕被和谐所以把内容打码了
比测试组给出的答案多出了7个,不知道是我重复了还是测试组的答案漏了
2.3计算模块部分异常处理说明
IO操作时用try-catch-finally进行异常处理,常见的是文件不存在时抛出IOException
三、心得
比较有成就感,刚开始看到题目的时候就觉得对我来说这题基本做不出来,尤其是看到拼音、首字母、谐音字的时候心态就有点崩,因为自己平时上网的时候就看到很多谐音字首字母的敏感词汇都没有被屏蔽,就觉得这个是一个不可能的任务。但是最后还是把一些觉得很难实现的情况给实现了,不知道对不对但是至少样例上的能输出了(说实话13号的时候还只想着做个最基本的然后摆烂,还好最后还是挣扎了一下)。
消耗了很多时间和精力,但是也学到了很多新知识比如DFA算法以及性能分析工具的使用还有单元测试覆盖率,也锻炼了使用搜索引擎的能力吧,还是很有收获的。只是题目描述不够清楚,之前说不能读取其他文件,我只能自己构造拆分汉字的映射,后面说可以读取,但是没时间再改了。。。
程序对不对不知道,不过也是尽力了