文本文件单词统计
问题描述
编写一个文本文件单词统计的程序,包括建立文件、单词统计、单词查询、单词定位的功能。
基本要求
程序应先询问用户的 ID号(ID 号包括两个大写字母和4 位数字),例如:
请输入用户 ID 号:AB1234
程序应对输入的 ID 号验证,符合 ID 号要求的格式,然后程序提示四种选择:
- 建立文件
- 单词统计
- 单词查询及定位
- 退出
注意:
- 文件至少包含50个英文单词(一定出现重复的单词,且一定包含数字)
- 文档不规范,单词之间可能不只有一个空格,并且有加引号的一些单词“jiaozi” 加引号的单词算单词,但数字不算单词
- 逐行扫描文本文件,计算文档中单词总数,及各个单词出现的频次,并且按照单词首字母abcd…… 的顺序排列,显示并生成soft.txt文件
- 查询任意给出的单词是否存在,如果不存在,在屏幕上输出“查无此词!”;如果存在,显示单词 出现的总次数,并且详细列出其 出现的位置。
例如:
请您输入待查询的词:
of 单词 of 共出现了2次;
第1次出现在第1行,第5个位置;
第2次出现在第3行,第1个位置。
请您输入待查询的词 :
代码实现
user类:规定了正确的用户格式ID
package Word_count.Demo;
public class User {
private String id;
private static final int len = 6;
public String getId() {
return id;
}
public boolean setId(String id) {//用来判断是否为规定的输入格式
boolean result = isAuthorized(id);
if (result)
this.id = id;
return result;
}
//isAuthorized方法用来规定正确的用户格式
public boolean isAuthorized(String id) {
char[] tmp = id.toCharArray();
if (tmp.length != 6)
return false;
for (int i = 0; i < len; i++) {
//通过ascll码来判断输入的格式是否正确
if (i < 2) {
if (tmp[i] < 65 || tmp[i] > 90)
return false;
}
else {
if (tmp[i] < 48 || tmp[i] > 57)
return false;
}
} // for
return true;
}
}
Processor类:
package Word_count.Demo;
import java.io.*;
import java.util.ArrayList;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.regex.Pattern;
public class Processor {
private String path; // 文件路径
private Map<String, ArrayList> hashTab; // 逻辑输出 单词:出现位置组成的链表始址
private static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
//在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度,且不要在方法体内定义 Pattern pattern = Pattern.compile(规则);
public Map<String, ArrayList> getHashTab() {
return hashTab;
}
public Processor() {
this.path = null;
this.hashTab = new TreeMap<>();
}
public void touchFile(User user) throws IOException {
String dirName = "D:\\files\\" + user.getId();
new File(dirName).mkdirs();
System.out.print("请输入文件名:");
Scanner sc = new Scanner(System.in);
path = dirName + "\\" + sc.next();//在上面创建的文件夹下创建文件的路径
File file = new File(path);
if (file.exists()) {
System.out.println("文件已存在!");
return;
}
file.createNewFile();
FileWriter writer = new FileWriter(file.getPath());
// 向文件写入内容
System.out.println("请输入文本:(按'quit'退出输入)");
String str = "";
while (sc.hasNextLine()) {//判断是否有键盘输入
str = sc.nextLine();//并将所输入的数据读取备用
if (str.equals("quit"))
break;
writer.write(str + '\n');
}
writer.flush();
writer.close();
}
public String whichFile(User user) {
System.out.println("是否选择当前文件?" + path);
System.out.println("输入1确定");
byte useCurrent = new Scanner(System.in).nextByte();
if (useCurrent == 0 || path == null) {
this.hashTab.clear();
System.out.print("请输入想要 统计词频/单词查询 的文件名");
path = "D:\\files\\" + user.getId() + "\\" + new Scanner(System.in).next();
}
return path;
}
public static String stringFormatting(String str) {//规范化,对字符串进行分割
str = str.replaceAll("\"", " ");//引号
str = str.replaceAll("\\p{Punct}", " ");//去除所有的标点符号
str = str.replaceAll("\\s+"," ");//任何空白字符,空格,制表符
str = str.toLowerCase();
return str;
}
public static boolean neededToBeCounted(String str) {
boolean isNumeric = (str != null && NUMBER_PATTERN.matcher(str).matches());
return !isNumeric;
}
public void wordFrequency() throws FileNotFoundException {
try {
hashTab.clear();//每次查找前先将哈希表清空
File file = new File(path);
BufferedReader in = new BufferedReader(new FileReader(file.getPath()));
String str;
String[] strArr;
int row = 0;
while ((str = in.readLine()) != null) {
str = stringFormatting(str);
strArr = str.split(" ");
for (int col = 0; col < strArr.length; col++) {
if (neededToBeCounted(strArr[col])) {
if (!hashTab.containsKey(strArr[col])) {
ArrayList<Integer[]> list = new ArrayList<>();
hashTab.put(strArr[col], list);
list.add(new Integer[]{row + 1, col + 1});
} else
hashTab.get(strArr[col]).add(new Integer[]{row + 1, col + 1});
}
} // for
row++;
} // while
} // try
catch (IOException e) {
path = null;
System.out.println("文件不存在!");
}
}
public void printResult() {
int cnt = 0;
for (String str: hashTab.keySet()
) {
int tmp = hashTab.get(str).size();
cnt += tmp;
System.out.println("单词:" + str + ' ' + ' ' + "出现次数:" + tmp);
}
System.out.println("单词总数:" + cnt);
}
public void query(String word) {
if (hashTab.containsKey(word)) {
ArrayList<Integer[]> list = hashTab.get(word);
int frequency = list.size(); //各出现位置所组成的链表的长度即为该单词出现的次数
System.out.println("单词" + word + "共出现了" + frequency + "次");
int i = 1;
for (Integer[] tmp: list
) {
System.out.println("第" + i++ + "次" + "出现在第" + tmp[0] + "行" + ",第" + tmp[1] + "个位置。");
}
} // if (hashTab.containsKey(word))
else {
System.out.println("查无此词!");
}
}
}
main类:
package Word_count.Demo;
import java.io.IOException;
import java.util.Scanner;
public class main {
public static void main(String[] args) throws IOException {
//用户ID输入及判断
Scanner sc = new Scanner(System.in);
User user = new User();
System.out.println("正确的输入格式为:AA1111");
System.out.print("请输入用户ID号:");
while (!user.setId(sc.next()))
System.out.println("id号不合法,请重新输入");
int option;//用于Swich语句判断进行什么操作
Processor pc = new Processor();
while (true) {
System.out.println("(1)建立文件");
System.out.println("(2)单词统计");
System.out.println("(3)单词查询及定位");
System.out.println("(4)退出");
do {
option = new Scanner(System.in).nextInt();
} while (option < 1 || option > 4);
if (4 == option)
break;
switch (option) {
//创建文件以及输入文本
case 1:
pc.touchFile(user);
break;
//输入要访问的文件名,将文件中内容读入内存,得到单词数量
case 2:
pc.whichFile(user);
pc.wordFrequency();
pc.printResult();
break;
case 3:
pc.whichFile(user);
if (pc.getHashTab().isEmpty())
pc.wordFrequency();
String str;
while (sc.hasNextLine()) {//等待键盘输入
System.out.print("请您输入待查询的词:(按'quit'退出)");
str = sc.next();
if (str.equals("quit"))
break;
else System.out.println("输入的单词没有查询到,请重新输入词典中已有的单词");
pc.query(str);
}
break;
}
}
}
}
设计想法
- 使用一个map容器存储单词信息其key,value键值对对应为<String,ArrayList>,key为string存储某个单词,ArrayList存储的是一个二维数组,即存储单词出现的位置行列,其长度为该单词出现的次数。
- 用户ID验证,以及文本文件的读取在大一时的java课设已经做过,这里不再进行赘述。
- 对于出现的文档不规范问题,通过查阅学习可以使用正则表达式的方式将所有的字符串中不规范字符用空替换。可以看这篇博客进行了解一下正则表达式的使用。java正则表达式详解_二十同学的博客-优快云博客_java正则表达式详解
- 统计单词位置,如1所示通过ArrayList的值存储其所在的位置,链表的第一个为第一次出现,后续与之相同。在读取文档时,可以使用逐行读取的方式,调用3的规范化方法,将文档规范后,单词之间都使用空格所分割,使用split方法将其分开存入strarr数组,其对应的下标即为所在的列。
后续查询位置与单词个数只需要输出ArrayList的size和其存储的二维数组的值。可能会有人看不懂代码内的foreach遍历方式,通过这篇博客可以了解一下java的foreach遍历。for-each 循环_恺_K的博客-优快云博客_for-each
又因为,我们的ArrayList中存储的是单词出现的行列,即二维数组的存储方式。JAVA中使用for-each遍历二维数组_佑佑有话说的博客-优快云博客_java二维数组foreach