软工实践作业(五)


GitHub

作业链接

结对博客 031602240


具体分工

许郁杨:WordCount代码、文档编写;

温伊倩:爬虫、附加功能设计和实现、部分文档编写.

我们首先详细阅读了作业要求,明确了各部分功能、实现方式和细节,以及所需的附加功能。

确定好需求和设计细节后,我们开始准备实现各自负责的部分,学习和测试需要使用到的技术。接着便是逐步完成各个功能,进行性能分析和单元测试,并编写博客。

在爬虫和附加功能部分,我主要是作为“驾驶员”(Driver),而队友主要作为“领航员”(Navigator);

在WordCount部分,我主要是作为“领航员”(Navigator),而队友主要作为“驾驶员”(Driver)。

这样分工使得两人工作量较为均等,并且各自都能完成较为擅长的部分,保证了最后的质量。


PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 45 57
· Estimate · 估计这个任务需要多少时间 45 57
Development 开发 890 1015
· Analysis · 需求分析 (包括学习新技术) 200 214
· Design Spec · 生成设计文档 30 32
· Design Review · 设计复审 20 9
· Coding Standard · 代码规范(为目前的开发制定合适的规范) 10 5
· Design · 具体设计 30 34
· Coding · 具体编码 400 518
· Code Review · 代码复审 50 41
· Test · 测试(自我测试,修改代码,提交修改) 150 162
Reporting 报告 70 47
· Test Report · 测试报告 20 12
· Size Measurement · 计算工作量 20 11
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 24
合计 1005 1119

代码规范

代码规范我们用的是实验室的代码规范:阿里巴巴的码出高效,并加上了一些补充


解题思路与设计说明


爬虫使用

  • 介绍

爬虫是用java的一款HTML解析器——Jsoup来实现的。

首先用Jsoup.connect(String url)抓取url,得到网站的HTML文件文档,根据代码发现各论文页面的地址放置于ptitle类a元素的href属性中,使用Element类得到论文页面的超链接后,再循环得到各论文页面的文档,使用Element类找到papertitle和abstract,将其文本存于字符串中输出到文件中。

  • 流程图

  • 主要代码
  1. //从URL加载Document
  2. Document doc = Jsoup.connect(URL)
  3. // 取消获取相应内容大小限制
  4. .maxBodySize(0)
  5. //设置超时时间
  6. .timeout(600000)
  7. .get();
  8. //ptitle类
  9. Elements paper = doc.select("[class=ptitle]");
  10. //ptitle类中带有href属性的a元素
  11. Elements links = paper.select("a[href]");
  12. //论文计数
  13. long count = 0;
  14. for(Element link : links) {
  15. //论文页url
  16. String url = link.absUrl("href");
  17. Document paperDoc = Jsoup.connect(url)
  18. .maxBodySize(0)
  19. .timeout(600000)
  20. .get();
  21. //获取论文标题
  22. Elements paperTitle = paperDoc.select("[id=papertitle]");
  23. String title = paperTitle.text();
  24. //获取论文简介
  25. Elements paperAbstract = paperDoc.select("[id=abstract]");
  26. String abstracts = paperAbstract.text();

代码组织与内部实现设计(类图)


算法关键


实现方法

这次基本要求里相对上次新增加的功能主要有以下几点:

  • 可传入多参数;
  • 可指定输入输出文件;
  • 词频统计权重开关;
  • 可指定词频统计输出个数;
  • 可统计词组词频,并可指定词组长度;
  • 可切换单词词频和词组词频;

其中实现的关键点在于多参数词组词频统计

对于多参数,我的实现方式是使用Apache的commons-cli包,并增设JavaBean "WordCounterInfo"

通过commons-cli中的PosixParser解析原始命令行数据,然后把解析得到的数据存入Bean。

其中,Bean所包含的参数有:

  1. private String inputFile = "input.txt";
  2. private String outputFile = "output.txt";
  3. private int weightFactor = 1;
  4. private int phraseLength = -1;
  5. private int wordFrequencyOutNum = 10;

对于词组词频统计,我的实现方式通过自动机扫描文本,判断得到单词,并记录下该单词的首尾下标,存入队列。然后判断队列长度,每当长度满足要求时,分别取出头尾两个单词的首下标、尾下标,这样就能定位出一整个词组。同时,在自动机判断过程中,每当在单词与单词的间隔中出现不属于分隔符的字母符号时,就清空队列,避免出现不合法的词组。


流程图


附加题

设计的创意独到之处

1.从网站爬取了论文作者、pdf链接的额外信息.[内容txt文件](https://files.cnblogs.com/files/qvq-qvq/result.txt.zip)
2.分析了论文列表中第一作者与第二作者之间的合作关系,并根据关系生成了关系图谱。

实现思路

1.实现方法如同爬虫使用。
2.由爬取的论文作者中提取第一作者与第二作者,用Java将之写入到Excel表格中,再使用NodeXL生成关系图表,对生成图进行筛选,可以得到合作数较多的作者。

实现成果展示

1.爬取信息图



2.全部作者关系图



筛选掉近发表过一次的作者

细节图(单节点说明此作者的度>=2,但队友都被滤掉了):



身为第一第二作者且和人合作次数的作者


关键代码

这里贴出多参数解析词组词频统计两个关键部分的代码,并做更详尽的分析解释。

对于多参数解析,我首先添加各项参数及其对应解释,如对于参数i,其意义为"input",后跟数据——输入文件的文件名,具体描述为"input file path."。

然后创建Posix形式的解析器,并解析命令行。接着逐项处理参数,对于i、o、w三个必有参数,直接取值并存入Bean中;对于其他可选参数,逐项判断。

  1. /**
  2. * 解析命令行
  3. *
  4. * @param args 命令行参数
  5. * @param wordCounterInfo 计数器的Bean
  6. */
  7. public static void parseCommadLine(String[] args, WordCounterInfo wordCounterInfo) {
  8. Options options = new Options();
  9. options.addOption("i", "input", true, "input file path.");
  10. options.addOption("o", "output", true, "result file path.");
  11. options.addOption("w", "weight", true, "set weight factor.");
  12. options.addOption("m", "length", true, "phrase length.");
  13. options.addOption("n", "number", true, "word frequency output number.");
  14. options.addOption("h", "help", false, "print options' information");
  15. CommandLineParser parser = new PosixParser();
  16. try {
  17. CommandLine commandLine = parser.parse(options, args);
  18. if (commandLine.hasOption("h")) {
  19. HelpFormatter helpFormatter = new HelpFormatter();
  20. helpFormatter.printHelp("Options", options);
  21. } else {
  22. wordCounterInfo.setInputFile(commandLine.getOptionValue("i"));
  23. wordCounterInfo.setOutputFile(commandLine.getOptionValue("o"));
  24. wordCounterInfo.setWeightFactor(Integer.parseInt(commandLine.getOptionValue("w")));
  25. if (commandLine.hasOption("m")) {
  26. wordCounterInfo.setPhraseLength(Integer.parseInt(commandLine.getOptionValue("m")));
  27. }
  28. if (commandLine.hasOption("n")) {
  29. wordCounterInfo.setWordFrequencyOutNum(Integer.parseInt(commandLine.getOptionValue("n")));
  30. }
  31. }
  32. } catch (ParseException e) {
  33. System.out.println("Arguments format wrong.");
  34. e.printStackTrace();
  35. }
  36. }

对于词组词频统计,基本逻辑与之前处理单词词频时相近。首先读入文本,然后判断是否为Title或Abstract,对这两个部分的文本区分处理。

通过自动机扫描单词,并记录下单词的首尾下标。每扫描出一个单词,就去构造词组,将新得到的单词压入队列尾部。自动机扫描过程中,如果出现一个合法单词后跟着一个非法单词的情况,就清空队列(此时那个合法单词不能在后续过程中组成合法词组)。如果队列长度满足要求,就记录下词组的首尾下标,并推出头元素。然后根据下标取出词组,存入Map。如果有权重要求,就对Title部分的词组增加权重值。最后对得到的Map排序,就得到了所需的词频排序列表。

  1. /**
  2. * 读取并计算Title和Abstract词组词频.
  3. *
  4. * @param fileName 文件名
  5. * @param weightFactor 权重参数
  6. * @param phraseLength 词组长度
  7. * @return 各词组词频
  8. */
  9. public static HashMap<String, Long> countPhraseFrequency(String fileName, int weightFactor, int phraseLength) {
  10. InputStreamReader inputStreamReader = null;
  11. BufferedReader bufferedReader = null;
  12. String in = "";
  13. char temp;
  14. int state = 0;
  15. int startSubscript = 0;
  16. int endSubscript = 0;
  17. HashMap<String, Long> phraseMap = new HashMap<String, Long>(100 * 1024 * 1024);
  18. //读入文件
  19. try {
  20. inputStreamReader = new InputStreamReader(new FileInputStream(fileName));
  21. } catch (FileNotFoundException e) {
  22. System.out.println("PhraseFrequencyCounter找不到此文件");
  23. e.printStackTrace();
  24. }
  25. if (inputStreamReader != null) {
  26. bufferedReader = new BufferedReader(inputStreamReader);
  27. }
  28. //计算单词词频
  29. try {
  30. while ((in = bufferedReader.readLine()) != null) {
  31. if (in.contains("Title: ")) {
  32. wordsDeque.clear();
  33. int length = in.length();
  34. state = 0;
  35. for (int i = 7; i < length; i++) {
  36. temp = in.charAt(i);
  37. //大写字母转为小写字母
  38. if ((temp >= 65) && (temp <= 90)) {
  39. temp += 32;
  40. }
  41. //自动机状态转移
  42. switch (state) {
  43. case 0: {
  44. if ((temp >= 97) && (temp <= 122)) {
  45. startSubscript = i;
  46. state = 1;
  47. }
  48. break;
  49. }
  50. case 1: {
  51. if ((temp >= 97) && (temp <= 122)) {
  52. state = 2;
  53. } else {
  54. wordsDeque.clear();
  55. state = 0;
  56. }
  57. break;
  58. }
  59. case 2: {
  60. if ((temp >= 97) && (temp <= 122)) {
  61. state = 3;
  62. } else {
  63. wordsDeque.clear();
  64. state = 0;
  65. }
  66. break;
  67. }
  68. case 3: {
  69. if ((temp >= 97) && (temp <= 122)) {
  70. endSubscript = i;
  71. state = 4;
  72. } else {
  73. wordsDeque.clear();
  74. state = 0;
  75. }
  76. break;
  77. }
  78. case 4: {
  79. if (((temp >= 97) && (temp <= 122)) || ((temp >= '0') && (temp <= '9'))) {
  80. endSubscript = i;
  81. } else {
  82. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  83. StringBuilder phrase = new StringBuilder();
  84. int start = phraseInfo.getStartSubscript();
  85. int end = phraseInfo.getEndSubscript();
  86. char tempc;
  87. for (int j = start; j <= end; j++) {
  88. tempc = in.charAt(j);
  89. if ((tempc >= 65) && (tempc <= 90)) {
  90. tempc += 32;
  91. }
  92. phrase.append(tempc);
  93. }
  94. if (weightFactor == 1) {
  95. if (phraseMap.containsKey(phrase.toString())) {
  96. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 10L);
  97. } else {
  98. phraseMap.put(phrase.toString(), 10L);
  99. }
  100. } else {
  101. if (phraseMap.containsKey(phrase.toString())) {
  102. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  103. } else {
  104. phraseMap.put(phrase.toString(), 1L);
  105. }
  106. }
  107. }
  108. state = 0;
  109. }
  110. break;
  111. }
  112. }
  113. }
  114. if (state == 4) {
  115. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  116. StringBuilder phrase = new StringBuilder();
  117. int start = phraseInfo.getStartSubscript();
  118. int end = phraseInfo.getEndSubscript();
  119. char tempc;
  120. for (int j = start; j <= end; j++) {
  121. tempc = in.charAt(j);
  122. if ((tempc >= 65) && (tempc <= 90)) {
  123. tempc += 32;
  124. }
  125. phrase.append(tempc);
  126. }
  127. if (weightFactor == 1) {
  128. if (phraseMap.containsKey(phrase.toString())) {
  129. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 10L);
  130. } else {
  131. phraseMap.put(phrase.toString(), 10L);
  132. }
  133. } else {
  134. if (phraseMap.containsKey(phrase.toString())) {
  135. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  136. } else {
  137. phraseMap.put(phrase.toString(), 1L);
  138. }
  139. }
  140. }
  141. }
  142. } else {
  143. if (in.contains("Abstract: ")) {
  144. wordsDeque.clear();
  145. int length = in.length();
  146. state = 0;
  147. for (int i = 10; i < length; i++) {
  148. temp = in.charAt(i);
  149. //大写字母转为小写字母
  150. if ((temp >= 65) && (temp <= 90)) {
  151. temp += 32;
  152. }
  153. //自动机状态转移
  154. switch (state) {
  155. case 0: {
  156. if ((temp >= 97) && (temp <= 122)) {
  157. startSubscript = i;
  158. state = 1;
  159. }
  160. break;
  161. }
  162. case 1: {
  163. if ((temp >= 97) && (temp <= 122)) {
  164. state = 2;
  165. } else {
  166. wordsDeque.clear();
  167. state = 0;
  168. }
  169. break;
  170. }
  171. case 2: {
  172. if ((temp >= 97) && (temp <= 122)) {
  173. state = 3;
  174. } else {
  175. wordsDeque.clear();
  176. state = 0;
  177. }
  178. break;
  179. }
  180. case 3: {
  181. if ((temp >= 97) && (temp <= 122)) {
  182. endSubscript = i;
  183. state = 4;
  184. } else {
  185. wordsDeque.clear();
  186. state = 0;
  187. }
  188. break;
  189. }
  190. case 4: {
  191. if (((temp >= 97) && (temp <= 122)) || ((temp >= '0') && (temp <= '9'))) {
  192. endSubscript = i;
  193. } else {
  194. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  195. StringBuilder phrase = new StringBuilder();
  196. int start = phraseInfo.getStartSubscript();
  197. int end = phraseInfo.getEndSubscript();
  198. char tempc;
  199. for (int j = start; j <= end; j++) {
  200. tempc = in.charAt(j);
  201. if ((tempc >= 65) && (tempc <= 90)) {
  202. tempc += 32;
  203. }
  204. phrase.append(tempc);
  205. }
  206. if (phraseMap.containsKey(phrase.toString())) {
  207. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  208. } else {
  209. phraseMap.put(phrase.toString(), 1L);
  210. }
  211. }
  212. state = 0;
  213. }
  214. break;
  215. }
  216. }
  217. }
  218. if (state == 4) {
  219. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  220. StringBuilder phrase = new StringBuilder();
  221. int start = phraseInfo.getStartSubscript();
  222. int end = phraseInfo.getEndSubscript();
  223. char tempc;
  224. for (int j = start; j <= end; j++) {
  225. tempc = in.charAt(j);
  226. if ((tempc >= 65) && (tempc <= 90)) {
  227. tempc += 32;
  228. }
  229. phrase.append(tempc);
  230. }
  231. if (phraseMap.containsKey(phrase.toString())) {
  232. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  233. } else {
  234. phraseMap.put(phrase.toString(), 1L);
  235. }
  236. }
  237. }
  238. }
  239. }
  240. }
  241. } catch (IOException e) {
  242. e.printStackTrace();
  243. } finally {
  244. try {
  245. inputStreamReader.close();
  246. } catch (IOException e) {
  247. e.printStackTrace();
  248. }
  249. }
  250. return phraseMap;
  251. }
  252. /**
  253. * 构造词组
  254. *
  255. * @param startSubscript 单词首下标
  256. * @param endSubscript 单词尾下标
  257. * @param phraseLength 词组长度
  258. * @return 当前是否构造出合法词组
  259. */
  260. private static boolean constructPhrase(int startSubscript, int endSubscript, int phraseLength) {
  261. WordInfo wordInfo = new WordInfo();
  262. wordInfo.setStartSubscript(startSubscript);
  263. wordInfo.setEndSubscript(endSubscript);
  264. wordsDeque.addLast(wordInfo);
  265. if (wordsDeque.size() == phraseLength) {
  266. phraseInfo.setStartSubscript(wordsDeque.getFirst().getStartSubscript());
  267. phraseInfo.setEndSubscript(wordsDeque.getLast().getEndSubscript());
  268. wordsDeque.removeFirst();
  269. return true;
  270. }
  271. return false;
  272. }

性能分析

下面是命令行参数为"-i result.txt -o output.txt -w 1 -n 20"的性能分析情况。









下面是命令行参数为"-i result.txt -o output.txt -w 1 -n 20 -m 3"的性能分析情况。









可以看出消耗最高的为单词和词组的词频统计部分。

  1. /**
  2. * 读取并计算Title和Abstract词频.
  3. *
  4. * @param fileName 文件名
  5. * @param weightFactor 权重参数
  6. * @return 各单词词频
  7. */
  8. public static HashMap<String, Long> countWordsFrequency(String fileName, int weightFactor) {
  9. InputStreamReader inputStreamReader = null;
  10. BufferedReader bufferedReader = null;
  11. String in = "";
  12. char temp;
  13. int state = 0;
  14. StringBuilder word = new StringBuilder();
  15. HashMap<String, Long> wordMap = new HashMap<String, Long>(100 * 1024 * 1024);
  16. //读入文件
  17. try {
  18. inputStreamReader = new InputStreamReader(new FileInputStream(fileName));
  19. } catch (FileNotFoundException e) {
  20. System.out.println("WordsFrequencyCounter找不到此文件");
  21. e.printStackTrace();
  22. }
  23. if (inputStreamReader != null) {
  24. bufferedReader = new BufferedReader(inputStreamReader);
  25. }
  26. //计算单词词频
  27. try {
  28. while ((in = bufferedReader.readLine()) != null) {
  29. if (in.contains("Title: ")) {
  30. word.setLength(0);
  31. int length = in.length();
  32. state = 0;
  33. for (int i = 7; i < length; i++) {
  34. temp = in.charAt(i);
  35. //大写字母转为小写字母
  36. if ((temp >= 65) && (temp <= 90)) {
  37. temp += 32;
  38. }
  39. //自动机状态转移
  40. switch (state) {
  41. case 0: {
  42. if ((temp >= 97) && (temp <= 122)) {
  43. word.append(temp);
  44. state = 1;
  45. }
  46. break;
  47. }
  48. case 1: {
  49. if ((temp >= 97) && (temp <= 122)) {
  50. word.append(temp);
  51. state = 2;
  52. } else {
  53. word.setLength(0);
  54. state = 0;
  55. }
  56. break;
  57. }
  58. case 2: {
  59. if ((temp >= 97) && (temp <= 122)) {
  60. word.append(temp);
  61. state = 3;
  62. } else {
  63. word.setLength(0);
  64. state = 0;
  65. }
  66. break;
  67. }
  68. case 3: {
  69. if ((temp >= 97) && (temp <= 122)) {
  70. word.append(temp);
  71. state = 4;
  72. } else {
  73. word.setLength(0);
  74. state = 0;
  75. }
  76. break;
  77. }
  78. case 4: {
  79. if (((temp >= 97) && (temp <= 122)) || ((temp >= '0') && (temp <= '9'))) {
  80. word.append(temp);
  81. } else {
  82. if (weightFactor == 1) {
  83. if (wordMap.containsKey(word.toString())) {
  84. wordMap.put(word.toString(), wordMap.get(word.toString()) + 10L);
  85. } else {
  86. wordMap.put(word.toString(), 10L);
  87. }
  88. } else {
  89. if (wordMap.containsKey(word.toString())) {
  90. wordMap.put(word.toString(), wordMap.get(word.toString()) + 1L);
  91. } else {
  92. wordMap.put(word.toString(), 1L);
  93. }
  94. }
  95. word.setLength(0);
  96. state = 0;
  97. }
  98. break;
  99. }
  100. }
  101. }
  102. if (state == 4) {
  103. if (weightFactor == 1) {
  104. if (wordMap.containsKey(word.toString())) {
  105. wordMap.put(word.toString(), wordMap.get(word.toString()) + 10L);
  106. } else {
  107. wordMap.put(word.toString(), 10L);
  108. }
  109. } else {
  110. if (wordMap.containsKey(word.toString())) {
  111. wordMap.put(word.toString(), wordMap.get(word.toString()) + 1L);
  112. } else {
  113. wordMap.put(word.toString(), 1L);
  114. }
  115. }
  116. }
  117. } else {
  118. if (in.contains("Abstract: ")) {
  119. word.setLength(0);
  120. int length = in.length();
  121. state = 0;
  122. for (int i = 10; i < length; i++) {
  123. temp = in.charAt(i);
  124. //大写字母转为小写字母
  125. if ((temp >= 65) && (temp <= 90)) {
  126. temp += 32;
  127. }
  128. //自动机状态转移
  129. switch (state) {
  130. case 0: {
  131. if ((temp >= 97) && (temp <= 122)) {
  132. word.append(temp);
  133. state = 1;
  134. }
  135. break;
  136. }
  137. case 1: {
  138. if ((temp >= 97) && (temp <= 122)) {
  139. word.append(temp);
  140. state = 2;
  141. } else {
  142. word.setLength(0);
  143. state = 0;
  144. }
  145. break;
  146. }
  147. case 2: {
  148. if ((temp >= 97) && (temp <= 122)) {
  149. word.append(temp);
  150. state = 3;
  151. } else {
  152. word.setLength(0);
  153. state = 0;
  154. }
  155. break;
  156. }
  157. case 3: {
  158. if ((temp >= 97) && (temp <= 122)) {
  159. word.append(temp);
  160. state = 4;
  161. } else {
  162. word.setLength(0);
  163. state = 0;
  164. }
  165. break;
  166. }
  167. case 4: {
  168. if (((temp >= 97) && (temp <= 122)) || ((temp >= '0') && (temp <= '9'))) {
  169. word.append(temp);
  170. } else {
  171. if (wordMap.containsKey(word.toString())) {
  172. wordMap.put(word.toString(), wordMap.get(word.toString()) + 1L);
  173. } else {
  174. wordMap.put(word.toString(), 1L);
  175. }
  176. word.setLength(0);
  177. state = 0;
  178. }
  179. break;
  180. }
  181. }
  182. }
  183. if (state == 4) {
  184. if (wordMap.containsKey(word.toString())) {
  185. wordMap.put(word.toString(), wordMap.get(word.toString()) + 1L);
  186. } else {
  187. wordMap.put(word.toString(), 1L);
  188. }
  189. }
  190. }
  191. }
  192. }
  193. } catch (IOException e) {
  194. e.printStackTrace();
  195. } finally {
  196. try {
  197. inputStreamReader.close();
  198. } catch (IOException e) {
  199. e.printStackTrace();
  200. }
  201. }
  202. return wordMap;
  203. }
  204. /**
  205. * 读取并计算Title和Abstract词组词频.
  206. *
  207. * @param fileName 文件名
  208. * @param weightFactor 权重参数
  209. * @param phraseLength 词组长度
  210. * @return 各词组词频
  211. */
  212. public static HashMap<String, Long> countPhraseFrequency(String fileName, int weightFactor, int phraseLength) {
  213. InputStreamReader inputStreamReader = null;
  214. BufferedReader bufferedReader = null;
  215. String in = "";
  216. char temp;
  217. int state = 0;
  218. int startSubscript = 0;
  219. int endSubscript = 0;
  220. HashMap<String, Long> phraseMap = new HashMap<String, Long>(100 * 1024 * 1024);
  221. //读入文件
  222. try {
  223. inputStreamReader = new InputStreamReader(new FileInputStream(fileName));
  224. } catch (FileNotFoundException e) {
  225. System.out.println("PhraseFrequencyCounter找不到此文件");
  226. e.printStackTrace();
  227. }
  228. if (inputStreamReader != null) {
  229. bufferedReader = new BufferedReader(inputStreamReader);
  230. }
  231. //计算单词词频
  232. try {
  233. while ((in = bufferedReader.readLine()) != null) {
  234. if (in.contains("Title: ")) {
  235. wordsDeque.clear();
  236. int length = in.length();
  237. state = 0;
  238. for (int i = 7; i < length; i++) {
  239. temp = in.charAt(i);
  240. //大写字母转为小写字母
  241. if ((temp >= 65) && (temp <= 90)) {
  242. temp += 32;
  243. }
  244. //自动机状态转移
  245. switch (state) {
  246. case 0: {
  247. if ((temp >= 97) && (temp <= 122)) {
  248. startSubscript = i;
  249. state = 1;
  250. }
  251. break;
  252. }
  253. case 1: {
  254. if ((temp >= 97) && (temp <= 122)) {
  255. state = 2;
  256. } else {
  257. wordsDeque.clear();
  258. state = 0;
  259. }
  260. break;
  261. }
  262. case 2: {
  263. if ((temp >= 97) && (temp <= 122)) {
  264. state = 3;
  265. } else {
  266. wordsDeque.clear();
  267. state = 0;
  268. }
  269. break;
  270. }
  271. case 3: {
  272. if ((temp >= 97) && (temp <= 122)) {
  273. endSubscript = i;
  274. state = 4;
  275. } else {
  276. wordsDeque.clear();
  277. state = 0;
  278. }
  279. break;
  280. }
  281. case 4: {
  282. if (((temp >= 97) && (temp <= 122)) || ((temp >= '0') && (temp <= '9'))) {
  283. endSubscript = i;
  284. } else {
  285. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  286. StringBuilder phrase = new StringBuilder();
  287. int start = phraseInfo.getStartSubscript();
  288. int end = phraseInfo.getEndSubscript();
  289. char tempc;
  290. for (int j = start; j <= end; j++) {
  291. tempc = in.charAt(j);
  292. if ((tempc >= 65) && (tempc <= 90)) {
  293. tempc += 32;
  294. }
  295. phrase.append(tempc);
  296. }
  297. if (weightFactor == 1) {
  298. if (phraseMap.containsKey(phrase.toString())) {
  299. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 10L);
  300. } else {
  301. phraseMap.put(phrase.toString(), 10L);
  302. }
  303. } else {
  304. if (phraseMap.containsKey(phrase.toString())) {
  305. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  306. } else {
  307. phraseMap.put(phrase.toString(), 1L);
  308. }
  309. }
  310. }
  311. state = 0;
  312. }
  313. break;
  314. }
  315. }
  316. }
  317. if (state == 4) {
  318. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  319. StringBuilder phrase = new StringBuilder();
  320. int start = phraseInfo.getStartSubscript();
  321. int end = phraseInfo.getEndSubscript();
  322. char tempc;
  323. for (int j = start; j <= end; j++) {
  324. tempc = in.charAt(j);
  325. if ((tempc >= 65) && (tempc <= 90)) {
  326. tempc += 32;
  327. }
  328. phrase.append(tempc);
  329. }
  330. if (weightFactor == 1) {
  331. if (phraseMap.containsKey(phrase.toString())) {
  332. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 10L);
  333. } else {
  334. phraseMap.put(phrase.toString(), 10L);
  335. }
  336. } else {
  337. if (phraseMap.containsKey(phrase.toString())) {
  338. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  339. } else {
  340. phraseMap.put(phrase.toString(), 1L);
  341. }
  342. }
  343. }
  344. }
  345. } else {
  346. if (in.contains("Abstract: ")) {
  347. wordsDeque.clear();
  348. int length = in.length();
  349. state = 0;
  350. for (int i = 10; i < length; i++) {
  351. temp = in.charAt(i);
  352. //大写字母转为小写字母
  353. if ((temp >= 65) && (temp <= 90)) {
  354. temp += 32;
  355. }
  356. //自动机状态转移
  357. switch (state) {
  358. case 0: {
  359. if ((temp >= 97) && (temp <= 122)) {
  360. startSubscript = i;
  361. state = 1;
  362. }
  363. break;
  364. }
  365. case 1: {
  366. if ((temp >= 97) && (temp <= 122)) {
  367. state = 2;
  368. } else {
  369. wordsDeque.clear();
  370. state = 0;
  371. }
  372. break;
  373. }
  374. case 2: {
  375. if ((temp >= 97) && (temp <= 122)) {
  376. state = 3;
  377. } else {
  378. wordsDeque.clear();
  379. state = 0;
  380. }
  381. break;
  382. }
  383. case 3: {
  384. if ((temp >= 97) && (temp <= 122)) {
  385. endSubscript = i;
  386. state = 4;
  387. } else {
  388. wordsDeque.clear();
  389. state = 0;
  390. }
  391. break;
  392. }
  393. case 4: {
  394. if (((temp >= 97) && (temp <= 122)) || ((temp >= '0') && (temp <= '9'))) {
  395. endSubscript = i;
  396. } else {
  397. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  398. StringBuilder phrase = new StringBuilder();
  399. int start = phraseInfo.getStartSubscript();
  400. int end = phraseInfo.getEndSubscript();
  401. char tempc;
  402. for (int j = start; j <= end; j++) {
  403. tempc = in.charAt(j);
  404. if ((tempc >= 65) && (tempc <= 90)) {
  405. tempc += 32;
  406. }
  407. phrase.append(tempc);
  408. }
  409. if (phraseMap.containsKey(phrase.toString())) {
  410. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  411. } else {
  412. phraseMap.put(phrase.toString(), 1L);
  413. }
  414. }
  415. state = 0;
  416. }
  417. break;
  418. }
  419. }
  420. }
  421. if (state == 4) {
  422. if (constructPhrase(startSubscript, endSubscript, phraseLength)) {
  423. StringBuilder phrase = new StringBuilder();
  424. int start = phraseInfo.getStartSubscript();
  425. int end = phraseInfo.getEndSubscript();
  426. char tempc;
  427. for (int j = start; j <= end; j++) {
  428. tempc = in.charAt(j);
  429. if ((tempc >= 65) && (tempc <= 90)) {
  430. tempc += 32;
  431. }
  432. phrase.append(tempc);
  433. }
  434. if (phraseMap.containsKey(phrase.toString())) {
  435. phraseMap.put(phrase.toString(), phraseMap.get(phrase.toString()) + 1L);
  436. } else {
  437. phraseMap.put(phrase.toString(), 1L);
  438. }
  439. }
  440. }
  441. }
  442. }
  443. }
  444. } catch (IOException e) {
  445. e.printStackTrace();
  446. } finally {
  447. try {
  448. inputStreamReader.close();
  449. } catch (IOException e) {
  450. e.printStackTrace();
  451. }
  452. }
  453. return phraseMap;
  454. }

对于词组词频统计部分,我们一开始的想法是通过双重循环,对每个单词都去判断是否能以其为首组成词组。维护一个词组的字符串,当符合长度时便存入Map。这种做法虽然简单,但加上中间操作后的时间消耗比较大。所以经过试验,对其进行了改进,改为记录首尾下标,这样减少时间消耗,也能保存下单词间的分隔符。


单元测试

单元测试框架用的是JUnit4。

我总共设计了十二个单元测试,其中Main一个,三个字词计数部分各三个,单词词频计数部分一个,词组词频计数部分一个。

单元测试 测试项 被测试代码
CharCounterTest 分别测试普通字符、无标题无摘要和空格 CharCounter.java
WordCounterTest 分别测试普通单词、无标题无摘要和大小写单词 WordCounter.java
LineCounterTest 分别测试普通行、无标题无摘要和混合行 LineCounter.java
WordFrequencyCounterTest 测试混合单词 WordFrequencyCounter.java
PhraseFrequencyCounterTest 测试混合词组 PhraseFrequencyCounter.java
MainTest 测试空白文件 Main.java

部分测试代码

  1. import com.eventide.wordCount.dataprocess.service.WordCounter;
  2. import org.junit.Test;
  3. import static org.junit.Assert.assertEquals;
  4. public class WordCounterTest {
  5. @Test
  6. //测试正常单词
  7. public void wordCounterTest1() {
  8. long wordNum = WordCounter.countWord("normalWordTest.txt");
  9. assertEquals(3, wordNum);
  10. }
  11. @Test
  12. //测试无标题无摘要单词
  13. public void wordCounterTest2() {
  14. long wordNum = WordCounter.countWord("noTitleAbstractWordTest.txt");
  15. assertEquals(0, wordNum);
  16. }
  17. @Test
  18. //测试单词大小写
  19. public void wordCounterTest3() {
  20. long wordNum = WordCounter.countWord("upLowWordTest.txt");
  21. assertEquals(4, wordNum);
  22. }
  23. }

GitHub签入记录




遇到的困难

对于词组词频统计部分,我们总共尝试了三种方法。

一种是上文提到的,通过双重循环,对每个单词都去判断是否能以其为首组成词组。维护一个词组的字符串,当符合长度时便存入Map。这种做法虽然简单,但加上中间操作后的时间消耗比较大。

第二种是维护一个队列,每当有合法单词出现时就压入队列中。如果队列长度符合要求,就取出队列中保存的单词,拼接成词组,压入Map。这种方法虽然简单快捷,但在看到群里说到,不同分隔符算不同词组时就凉了。。如果要记入分隔符就需要将分隔符一起存下来。虽然可以将合法单词和其后的分隔符一起保存,但这种方法在实现上存在一些困难,在拼接成词组时还要对单词进行二次处理,因此我们觉得并不是合适的处理方式。

经过试验,我们使用了记录首尾下标的方式。创建一个JavaBean存下每个合法单词的首尾下标,通过下标组成队列,进而拼装出词组。这样做效率不错,处理过程也不复杂,因此我们认为是较为合适的方法。

我们还遇到了一个重大困难,就是国庆假期前两个人接连感冒发烧(都怪优秀的舍友),一直到现在也还没完全好。。我想解决方法,应该只有穿越时空解决掉舍友了吧。(肥宅怎么可能去锻炼身体.jpg


评价队友

值得学习的地方:

我的队友思维缜密,写的文档逻辑清晰条理分明;coding能力强,代码写的认真规范;认真负责,执行能力强,对时间的把控能力很好,所以不仅能按时完成任务,而且完成度很高。

需要改进的地方:

没有,吹爆我队友٩̋(ˊ•͈ ꇴ •͈ˋ)و


学习进度条

代码行数(新增/累积)/行 学习时间(新增/累积)/h 重要成长
目标
第四周 100 7
第五周 200 12

参考链接

Java 容器源码分析之 Deque 与 ArrayDeque

commons-cli使用介绍

软工实践第五次作业-WordCount进阶需求的更多相关文章

  1. 《软件工程实践》第五次作业-WordCount进阶需求 (结对第二次)

    在文章开头给出结对同学的博客链接.本作业博客的链接.你所Fork的同名仓库的Github项目地址 本作业博客链接 github pair c 031602136魏璐炜博客 031602139徐明盛博客 ...

  2. 《软工实践》第零次作业 - 一些QA

    <软工实践>第零次作业 - 一些QA Q&A (1)回想一下你初入大学时对计算机专业的畅想 当初你是如何做出选择计算机专业的决定的? 你认为过去两年中接触到的课程是否符合你对计算机 ...

  3. 福州大学2020年春软工实践W班第一次作业

    作业描述 这个作业属于哪个课程 福州大学2020年春软工实践W班 这个作业要求在哪里 寒假作业(1/2) 这个作业的目标 建立博客.回顾,我的初心.当下和未来.学习路线 作业正文 福州大学2020年春 ...

  4. 结队第二次作业——WordCount进阶需求

    结队第二次作业--WordCount进阶需求 博客地址 051601135 岳冠宇 博客地址 051604103 陈思孝 博客地址 Github地址 具体分工 队友实现了爬虫功能,我实现了wordco ...

  5. 软工实践 - 第三十次作业 Beta答辩总结

    福大软工 · 第十二次作业 - Beta答辩总结 组长本次博客作业链接 项目宣传视频链接 本组成员 1 . 队长:白晨曦 031602101 2 . 队员:蔡子阳 031602102 3 . 队员:陈 ...

  6. 结对第2次作业——WordCount进阶需求

    作业题目链接 队友链接 Fork的同名仓库的Github项目地址 具体分工 玮哥负责命令参数判断.单词权重统计,我只负责词组词频统计(emmmm). PSP表格 预估耗时(分钟) 实际耗时(分钟) P ...

  7. 软工实践 - 第二十一次作业 BETA 版冲刺前准备

    软工 · BETA 版冲刺前准备(团队) 过去存在的问题 组员之间缺乏沟通,前后端缺乏沟通协作 组员积极性不高 基础知识不够扎实 手动整合代码效率过低 我们已经做了哪些调整/改进 通过会议加强组员之间 ...

  8. 2015级软工实践k班第一次作业-准备

    第一次作业-准备······ 几篇文章阅读下来发现一个事实,还是要有明确的目标,清楚自己需要做什么最为重要.然后根据目标确定需要为之所做的准备工作,考研也好,工作也罢,都是服务于自己的目标. 问题答应 ...

  9. 软工实践 - 第十一次作业 Alpha 冲刺 (3/10)

    队名:起床一起肝活队 组长博客:https://www.cnblogs.com/dawnduck/p/9972061.html 作业博客:班级博客本次作业的链接 组员情况 组员1(队长):白晨曦 过去 ...

随机推荐

  1. 转载 - CNN感受野(receptive-fields)RF

    本文翻译自A guide to receptive field arithmetic for Convolutional Neural Networks(可能需要FQ才能访问),方便自己学习和参考.若 ...

  2. c# webbrowser控件内核版本强制修改

    int BrowserVer, RegVal; // get the installed IE version using (WebBrowser Wb = new WebBrowser()) Bro ...

  3. weblogic实时监控开发

    参考api文档 https://docs.oracle.com/cd/E13222_01/wls/docs90/wlsmbeanref/core/index.html https://docs.ora ...

  4. Windows10 + Visual Studio 2017 + CMake +OpenCV编译、开发环境配置及测试

    由于最近需要使用OpenCV,本人需要在自己的PC上使用OpenCV,因此最近一直在研究如何使用Visual Studio编译OpenCV源代码并搭建开发环境,折腾了很长时间,查阅了很多相关资料,终于 ...

  5. Spring的Aspect切面类不能拦截Controller中的方法

    根本原因在于<aop:aspectj-autoproxy />这句话是在spring的配置文件内,还是在springmvc的配置文件内.如果是在spring的配置文件内,则@Control ...

  6. Springboot分模块开发详解(1):建立父工程

    基础服务,见下: base是父工程,base-entity是实体层,base-dao是DAO层,base-service是业务层,base-controller是WEB控制器层,base-web是页面 ...

  7. 配置vCenter Server Appliance 6.7

    =============================================== 2019/4/14_第1次修改                       ccb_warlock == ...

  8. Account的简单架构

    前几天,有园友私下问我,博客中的AccountDemo后端架构为什么是那样的,是不是分层太多太冗余,故这里简单介绍下.先看解决方案工程截图: 每个工程的含义,见https://www.cnblogs. ...

  9. mysql当查询某字段结果为空并赋值

    1 代码 1.1 当当前字段为空,查询结果返回“none”,并且统计出现频率 select case when 字段 is null then 'none' else 字段 end as 字段, co ...

  10. GBDT、XGBOOST、LightGBM调参数

    总的认识: LightGBM  > XGBOOST  > GBDT 都是调参数比较麻烦. GBDT分类的最佳调参数的讲解: Gradient Boosting Machine(GBM)调参 ...