Java 文件练习(红楼梦)

OOP h13

现在有一个文本文件 red.txt(其格式和内容见 red.txt),里面存放了红楼梦 120 回的内容:

请完成 Analysis 类,增加完成适当的方法,使得该程序可以完成如下功能:

  1. 统计任意词在红楼梦中每回出现的频率:getStringFrequent(str)
  2. 不考虑空格、回车、所有标点符号、制表符的情况下,统计整个红楼梦中 topN 的两个字的组合: getTopNWords(n)

参考的输出结果见:参考运行结果

提示:Analysis 中已经有了 readFromTxt(不要修改这个方法,否则可能会读出乱码)和将红楼梦拆分为 120 回的方法。 将小说分成 120 个章节的时候,不用过分纠结小说中的干扰字符,例如: 手机电子书·大学生小说网…….更新时间:2006-7-26 11:43:00 本章字数:6394 等等

需要完成的有构造方法、统计频率和统计最高频词。

0x00 Analysis

构造方法传参数为文件地址,需要调用给出的 readFromTxt 方法读取文件,返回一个字符串。

由于后续操作需要分章节,所以在此处可以调用 splitContentToChapter 拆分章节,存放到字符串数组中。

注意,后续统计均忽略空格、回车、所有标点符号和制表符,为此需要将这些特殊字符存放起来用于后续判断。我将所有标点符号用正则表达式替换为空格,然后存放空格、回车和制表符为需要忽略的字符。

1
2
3
4
5
6
7
8
9
10
11
12
private final String[] chapters;
private final Set<Character> ignoreChar = new HashSet<>(Arrays.asList(' ', '\r', '\t'));

/**
* @throws Exception
*
*/
public Analysis(String filename) throws Exception {
String text = readFromTxt(filename);
text = text.replaceAll("[\\pP‘’“”]", " ");
this.chapters = splitContentToChapter(text);
}

0x01 getStringFrequent

统计每章节某字符串出现的次数。

共 120 章,所以建立大小为 120 的数组,第 i 个元素就是该字符串在第 i 章出现的次数。

注意需要统计的字符串长度不定。

因为上面拆分出来的字符串数组第 0 个元素为第一章之前的部分,所以循环从 1(第一章)开始,循环 120 次,对于每一次循环,遍历该章节的字符串,并取子串比较,若相等则自增 1。(此处可优化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int[] getStringFrequent(String str) throws Exception {
int[] counts = new int[120];
if (this.chapters.length > 121) {
throw new Exception("拆分的章节数量不对");
}
int i, j;
int length = str.length();
for (i = 1; i < this.chapters.length; i++) {
int count = 0;
String content = this.chapters[i];
for (j = 0; j < content.length() + 1 - length; j++) {
String contentStr = content.substring(j, j + length);
if (str.equals(contentStr)) {
count++;
}
counts[i - 1] = count;
}
}
return counts;
}

0x02 getTopNWords

返回红楼梦中出现频率最高的 N 个词,频率从高到低排列(所谓词就是两个相邻的汉字)。

同样,和之前做过的作业类似,用哈希映射,把所有词作为键存放其中,其出现的频率作为值,最后排个序。

如果统计过程中遇到需要忽略的字符,则跳过。

最终存放前 n 个键,即为高频词。

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
public List<String> getTopNWords(int n){
int i, j;
Map<String, Integer> map = new HashMap<>();
List<Map.Entry<String, Integer>> mapList;
List<String> ans = new ArrayList<>();
for (i = 1; i < this.chapters.length; i++){
String content = this.chapters[i];
for (j = 0; j < content.length() - 1; j++) {
String str = content.substring(j, j + 2);
if (ignoreChar.contains(str.charAt(0)) || ignoreChar.contains(str.charAt(1))) {
continue;
}
int count;
count = map.getOrDefault(str, 0);
map.put(str, count + 1);
}
}

mapList = new ArrayList<>(map.entrySet());
mapList.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));

for (i = 0; i < n; i++) {
ans.add(mapList.get(i).getKey());
}

return ans;
}

0x03 完整代码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

package com.huawei.classroom.student.h13;

import java.io.*;
import java.util.*;

/**
* 在本包下增加合适的类和方法,使得Test类能够测试通过
*
* 不要引用jdk1.8以外第三方的包
*
* @author super
*
*/
public class Analysis {
private final String[] chapters;
private final Set<Character> ignoreChar = new HashSet<>(Arrays.asList(' ', '\r', '\t'));

/**
* @throws Exception
*
*/
public Analysis(String filename) throws Exception {
String text = readFromTxt(filename);
text = text.replaceAll("[\\pP‘’“”]", " ");
this.chapters = splitContentToChapter(text);
}

/**
* 提示 :将一个文本文件读取到一个字符串中返回
*
* @param filename 红楼梦文本文件的全路径名
* @return 文本的内容
*/
private String readFromTxt(String filename) throws Exception {
Reader reader = null;
try {
StringBuffer buf = new StringBuffer();
char[] chars = new char[1024];
// InputStream in=new FileInputStream(filename);

reader = new InputStreamReader(new FileInputStream(filename), "UTF-8");
int readed = reader.read(chars);
while (readed != -1) {
buf.append(chars, 0, readed);
readed = reader.read(chars);
}
return buf.toString();
} finally {
close(reader);
}
}

/**
* 返回红楼梦中出现频率最高的N个次,频率从高到低排列(所谓词就是两个相邻的汉字)
* @param n
* @return
*/
public List<String> getTopNWords(int n){
int i, j;
Map<String, Integer> map = new HashMap<>();
List<Map.Entry<String, Integer>> mapList;
List<String> ans = new ArrayList<>();
for (i = 1; i < this.chapters.length; i++){
String content = this.chapters[i];
for (j = 0; j < content.length() - 1; j++) {
String str = content.substring(j, j + 2);
if (ignoreChar.contains(str.charAt(0)) || ignoreChar.contains(str.charAt(1))) {
continue;
}
int count;
count = map.getOrDefault(str, 0);
map.put(str, count + 1);
}
}

mapList = new ArrayList<>(map.entrySet());
mapList.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));

for (i = 0; i < n; i++) {
ans.add(mapList.get(i).getKey());
}

return ans;
}
/**
* 关闭输入输入流
*
* @param inout
*/
private void close(Closeable inout) {
if (inout != null) {
try {
inout.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 提示 将红楼梦文本文件拆分为120个章节的方法
*
* @param content
* @return 返回120个元素的字符串数组
*/
private String[] splitContentToChapter(String content) {
// 提示 使用 content.split(" 第[一,二,三,四,五,六,七,八,九,十,零]{1,5}回 ");正则表达拆分
// 百度一下正则表达式
String contents[] = content.split(" 第[一,二,三,四,五,六,七,八,九,十,零]{1,5}回 ");
return contents;
}


/**
* 统计红楼梦章节字符串str出现的频率
* @param str
* @return
* @throws Exception
*/
public int[] getStringFrequent(String str) throws Exception {
int[] counts = new int[120];
if (this.chapters.length > 121) {
throw new Exception("拆分的章节数量不对");
}
int i, j;
int length = str.length();
for (i = 1; i < this.chapters.length; i++) {
int count = 0;
String content = this.chapters[i];
for (j = 0; j < content.length() + 1 - length; j++) {
String contentStr = content.substring(j, j + length);
if (str.equals(contentStr)) {
count++;
}
counts[i - 1] = count;
}
}
return counts;
}

}