Java 英文词频统计
16
09 月

Java 英文词频统计

本内容基于txt文本进行统计,对于单词的时态,人称和拼写变种等直接使用2+2+3lem 替换,舍去撇号缩写单词后半部分(形如we’re只保留we),保留hyphen(-)连接的单词,并对结果进行翻译。

文本解析时进行正则表达式替换,然后将所得结果按空格扫描输入,按<单词, 词频>输入Map中,然后对Map进行按词频排序,再格式化输出到文本中。

相关资源下载:

结果示例:

统计于The Old Man and The Sea,与网上的一些版本有较大不同,主要来源于时态,单复数和大小写等的替换

总字数:26590   单词数:1790     @xinyo.org

   序号  单词                 次数    万分比   翻译                  
  ----  ----                 ----     ----   ----                
     1  the                  2316      871   art. 那;              
     2  he                   1396      525   int. 他;              
     3  and                  1259      473   conj. 和,与;           
     4  be                   1001      376   prep. 是,有,在;         
     5  I                     567      213   int. 我;              
     6  of                    540      203   prep. 属于;            
     7  it                    494      185   int. 它;              
     8  to                    454      170   prep. 到,向;           
     9  his                   446      167   pron. 他的;            
    10  a                     426      160   art. 一;

1.  读取txt文件

目标txt文件直接放在了Project的跟文件夹下,默认读取中文乱码,所以添加了codeType形参的read()方法

package org.xinyo.GetFreq;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class ReadFile {
	public static String read(String file){
		StringBuilder sb = new StringBuilder();
		try {
		      InputStreamReader isr = 
			new InputStreamReader(new FileInputStream(file));  
		      BufferedReader in = new BufferedReader(isr);
		      try {
		        String s;
		        while((s = in.readLine()) != null) {
		          sb.append(s);
		          sb.append("\n");
		        }
		      } finally {
		        in.close();
		      }
		    } catch(IOException e) {
		      throw new RuntimeException(e);
		    }
		    return sb.toString();
	}
	
	//codeType是为了考虑中文,单词翻译过程中使用
	public static String read(String file, String codeType){
		StringBuilder sb = new StringBuilder();
		try {
		      InputStreamReader isr = 
			new InputStreamReader(new FileInputStream(file),codeType);  
		      BufferedReader in = new BufferedReader(isr);
		      try {
		        String s;
		        while((s = in.readLine()) != null) {
		          sb.append(s);
		          sb.append("\n");
		        }
		      } finally {
		        in.close();
		      }
		    } catch(IOException e) {
		      throw new RuntimeException(e);
		    }
		    return sb.toString();
	}

}

2. 词典初始化(2+2+3lem.txt)

词典原始地址:2+2+3lem ,我选择了用这个穷举替换的方法。实际上 英文分词算法(Porter stemmer)有很多其他成熟的方案可以直接使用,如文章http://lutaf.com/212.htm 中提到的:

以上这些方法涉及语言学知识,我也没有深究(也看不懂),这些算法可以直接使用。

本方案中将变种写入Key,对应的原型写入Value,可能会有不同的Key对应相同的Value值

package org.xinyo.GetFreq;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import java.util.regex.Pattern;

public class Dict {
	public static HashMap<String, String> readDict(String pathdict){
		String dict = ReadFile.read(pathdict);//读取词典文件
		Pattern pt = Pattern.compile("\\s{4}.+");//词典变种单词行匹配
		ArrayList<String> arrL = new ArrayList<>();
		HashMap<String, String> dictMap = new HashMap<>();
		Scanner scDict = new Scanner(dict);
		while (scDict.hasNext()) {//将词典按行写入列表
			arrL.add(scDict.nextLine());	
		}
		for (int n = 1; n < arrL.size(); n++) {//遍历列表
			if (pt.matcher(arrL.get(n)).matches()) {//匹配变种单词行
				
				String[] spl = arrL.get(n).split(", ");//对变种单词进行分词,写入临时数组
				for (String string : spl) {//按<变种, 原型>写入Map
					string = string.replaceAll("\\s{4}", "");
					dictMap.put(string, arrL.get(n-1));
				}				
			}
		}
		scDict.close();	
		return dictMap;//返回Map字典			
	}

}

3. 翻译词典初始化

这个是个画蛇添足的东西,但还是保留了。最初被来想用Google或YouDao的翻译API,看了一眼Google的已经收费了(部分貌似免费),YouDao的我也懒申请APIkey,就直接再次采用穷举的暴力方法。

package org.xinyo.GetFreq;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TransDict {
	static HashMap<String, String> transRead(String tdPath) throws IOException{
		String dict = ReadFile.read(tdPath,"utf-8");//按utf-8编码读取文本
		ArrayList<String> arrL = new ArrayList<>();
		HashMap<String, String> transDictMap = new HashMap<>();
		Scanner scDict = new Scanner(dict);
		while (scDict.hasNext()) {//将词典按行写入列表
			arrL.add(scDict.nextLine());	
		}
		for (String string : arrL) {//将翻译写入Map
			Matcher m = Pattern.compile("^(.+)\\s{6}((.)+)").matcher(string);
			while(m.find()){ 		
				transDictMap.put(m.group(1), m.group(2));
			}
		}
		scDict.close();
		return transDictMap;		
	}
}

4. 目标文本读入与解析

  • "(\\d|\\r|\\n)" > ” ” //用空格替换数字和换行符
  • "(\\p{Punct}|\\s){2,}" > ” ” //用空格替换所有标点符号
  • "(\\w+)'\\w+" > “$1” //舍去撇号缩写单词后半部分

这里就是先正则表达式处理,然后按词典匹配变种,按<单词, 词频 >写入HashMap

package org.xinyo.GetFreq;

import java.util.HashMap;
import java.util.Scanner;
import java.util.regex.Pattern;

public class ParseFile {
	 public static HashMap<String, Integer> getFreq(String inPut, HashMap<String, String> dictMap){
		HashMap<String, Integer> mp = new HashMap<String, Integer>();//key:单词,value:词频
		Pattern regex1 = Pattern.compile("(\\d|\\r|\\n)");//空格替换数字和换行
		Pattern regex2 = Pattern.compile("(\\p{Punct}|\\s){2,}");//空格替换标点
		Pattern regex3 = Pattern.compile("(\\w+)'\\w+");//舍去上撇号之后内容
		inPut = regex1.matcher(inPut).replaceAll(" ");
		inPut = regex2.matcher(inPut).replaceAll(" ");
		inPut = regex3.matcher(inPut).replaceAll("$1");
		inPut = inPut.toLowerCase();//小写转换
		Scanner scanner = new Scanner(inPut);	
		while(scanner.hasNext()){
			String s = scanner.next();
			if(dictMap.containsKey(s)){//按词典匹配,覆盖变种单词
				s = dictMap.get(s);
			}
			if ((s.length()>1||s.equals("a")||s.equals("I"))||s.matches("(\\w|-|\\.)+")) {//纯字母单词或含-.单词
				Integer freq = mp.get(s);
				mp.put(s, freq == null ? 1 : freq + 1);	
			}		
		}
		scanner.close();
		return mp;	
	}
}

5. 按词频排序

按词频排序,也就是Map的Value排序,这里直接使用sort-a-mapkey-value-by-values-java 中的方案,将原方案的升序改为按值降序排序了。

package org.xinyo.GetFreq;

import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class SortMap{
	public static <K, V extends Comparable<? super V>> Map<K, V> sort( Map<K, V> map) throws IOException {
      List<Map.Entry<K, V>> list =
          new LinkedList<Map.Entry<K, V>>( map.entrySet() );
      Collections.sort( list, new Comparator<Map.Entry<K, V>>(){
    	  public int compare( Map.Entry<K, V> o1, Map.Entry<K, V> o2 ){
              return (o2.getValue()).compareTo( o1.getValue() );
          }
      } );

      Map<K, V> result = new LinkedHashMap<K, V>();
      for (Map.Entry<K, V> entry : list){
    	  result.put( entry.getKey(), entry.getValue() ); //排序后的Map

      }
      return result;
  }
}

6. 格式化输出

格式化输出主要是美观

package org.xinyo.GetFreq;

import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class OutPut {
	static int tl = 0;
	public static <K, V extends Comparable<? super V>> void fileWrite(Map<K, V> mapSorted, String pathout) throws IOException{
		HashMap<String, String> transDictMap = TransDict.transRead("stardict1.3.txt");//读入翻译词典
		for (K string : mapSorted.keySet()) {//统计总字数
			tl += (int) mapSorted.get(string);
		}
		FileWriter writer = new FileWriter(pathout);
	    writer.write("  总字数:" + tl + "   单词数:" + mapSorted.size() + "     @xinyo.org\r\n\r\n");
	    String title = String.format( "%4s  %-14s %6s %5s   %-20s\r\n", "序号", "单词", "次数", "万分比", "翻译");
	    String title2 = String.format( "%6s  %-16s %8s %8s   %-20s\r\n", "----", "----", "----", "----", "----");
	    writer.write(title);
	    writer.write(title2);
		String fm = new String();
		int i = 0;
		String trans = null;
		for (Map.Entry<K, V> entry : mapSorted.entrySet()){//遍历排序后的Map
			i++;
			String key = (String) entry.getKey();
			if(transDictMap.containsKey(key)){//匹配翻译词典
				trans = transDictMap.get(key);
			}else{
				key = key.substring(0,1).toUpperCase()+key.substring(1);
				if(transDictMap.containsKey(key)){
					trans = transDictMap.get(key);
				}else{
					trans = "";
					key = key.toLowerCase();
				}
			}
			fm = String.format( "%6d  %-16.16s %8d %8.8s   %-20.35s \r\n", i, key, entry.getValue(), 10000*(int)(entry.getValue())/tl, trans);
			writer.write(fm);
		}
		tl = 0;
        writer.flush();
        writer.close();	
	}
}

7. main()方法

注释掉的部分是对多个文件结果累加(mapPlusResult),统计多个文件的总词频

package org.xinyo.GetFreq;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class WordsCount {
	public static void main(String[] args) throws IOException {
		String[] p1 = {"theold.txt"};//统计目标文本,可以输入多个值
		String[] p2 = {"000.txt"};//输出文本
		Map<String, Integer> sortedMap = new LinkedHashMap<>();
//		Map<String, Integer> mapPlusResult = new HashMap<>();
		HashMap<String, String> dictMap = Dict.readDict("2+2+3lem.txt");//单词匹配词典	
		for (int i = 0; i < p1.length; i++) {
			p2[i] = "000 " + p1[i];
			String strIn = ReadFile.read(p1[i]);//读取文件
			HashMap<String, Integer> map= ParseFile.getFreq(strIn, dictMap);//格式化文本,写入Map
			sortedMap = SortMap.sort(map);//按值(词频)排序
			OutPut.fileWrite(sortedMap, p2[i]);//输出排序后的Map到文本
//			mapPlusResult = MapPlus.checkAndPlus(map, mapPlusResult);//对Map累加
		}
//		Map<String, Integer> plusSorted = SortMap.fMap(mapPlusResult);//对累加结果排序
//		OutPut.fileWrite(plusSorted, "0.txt");
	}

}

1 条评论

  1. xy24012017 年 04 月 19 日上午 10:35 回复

    最近也想做类似的东西。有许多帮助。