java 字符串分词_Java实现的双向匹配分词算法示例
本文实例讲述了Java实现的双向匹配分词算法。分享给大家供大家参考,具体如下:
目前比较流行的几大分词算法有:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。本文采用的是基于字符串匹配法。
正向最大匹配分词:
该算法是基于分词词典实现,从字符串左侧进行分割匹配,如果词典存在则返回分割出来的词语并将该词从之前的字符串中切除,循环进行切割直到字符串大小为0。
例如:str = "我们都是西北农林科技大学信息工程学院的学生。",(假设我们定义最大切割长度max = 8,也就是八个汉字)
1.定义分词长度len = max,从左边取出len个字符word = "我们都是西北农林",并在字典中匹配word;
2.字典中没有该词,那么去掉最后一个字符赋值给word,len值减1;
3.重复步骤2,直到在字典中找到该词或是len = 1,退出循环;
4.从str中切除掉已分割的词语,重复进行1、2、3步骤,直到str被分解完。
逆向最大匹配分词:
和正向分词算法一样,只是从字符串的右边开始切分词语,直到字符串长度为0,。这里不再赘述。
双向匹配分词:
该方法是在正向分词和逆向分词的基础上,对于分割有歧义的语句进行歧义分词。提出“贪吃蛇算法”实现。要进行分词的字符串,就是食物。有2个贪吃蛇,一个从左向右吃;另一个从右向左吃。只要左右分词结果有歧义,2条蛇就咬下一口。贪吃蛇吃下去的字符串,就变成分词。 如果左右分词一直有歧义,两条蛇就一直吃。两条蛇吃到字符串中间交汇时,就肯定不会有歧义了。这时候,左边贪吃蛇肚子里的分词,中间没有歧义的分词,右边贪吃蛇肚子里的分词,3个一拼,就是最终的分词结果。
本文是对整段话进行分词,首先将这段话根据标点符号断句,然后将每句话再进行分词输出。
package cn.nwsuaf.spilt;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**
* 基于正逆双向最大匹配分词算法实现
* @author 刘永浪
*
*/
public class WordSpilt {
/**
* 存储分词词典
*/
private Map map = new HashMap();
/**
* 最大切词长度,即为五个汉字
*/
private int MAX_LENGTH = 5;
/**
* 构造方法中读取分词词典,并存储到map中
*
* @throws IOException
*/
public WordSpilt() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("src/dict.txt"));
String line = null;
while ((line = br.readLine()) != null) {
line = line.trim();
map.put(line, 0);
}
br.close();
}
/**
* 设置最大切词长度
*
* @param max
* 最大切词长度
*/
public void setMaxLength(int max) {
this.MAX_LENGTH = max;
}
/**
* 获取当前最大切词长度,默认为5(5个汉字)
*
* @return 当前最大切词长度
*/
public int getMaxLength() {
return this.MAX_LENGTH;
}
/**
* 最大匹配分词算法
*
* @param spiltStr
* 待切分的字符串
* @param leftToRight
* 切分方向,true为从左向右,false为从右向左
* @return 切分的字符串
*/
public List spilt(String spiltStr, boolean leftToRight) {
// 如果带切分字符串为空则返回空
if (spiltStr.isEmpty())
return null;
// 储存正向匹配分割字符串
List leftWords = new ArrayList();
// 储存负向匹配分割字符串
List rightWords = new ArrayList();
// 用于取切分的字串
String word = null;
// 取词的长度,初始化设置为最大值
int wordLength = MAX_LENGTH;
// 分词操作中处于字串当前位置
int position = 0;
// 已经处理字符串的长度
int length = 0;
// 去掉字符串中多余的空格
spiltStr = spiltStr.trim().replaceAll("\\s+", "");
// 当待切分字符串没有被切分完时循环切分
while (length < spiltStr.length()) {
// 如果还没切分的字符串长度小于最大值,让取词词长等于该词本身长度
if (spiltStr.length() - length < MAX_LENGTH)
wordLength = spiltStr.length() - length;
// 否则取默认值
else
wordLength = MAX_LENGTH;
// 如果是正向最大匹配,从spiltStr的position处开始切割
if (leftToRight) {
position = length;
word = spiltStr.substring(position, position + wordLength);
}
// 如果是逆向最大匹配,从spiltStr末尾开始切割
else {
position = spiltStr.length() - length;
word = spiltStr.substring(position - wordLength, position);
}
// 从当前位置开始切割指定长度的字符串
// word = spiltStr.substring(position, position + wordLength);
// 如果分词词典里面没有切割出来的字符串,舍去一个字符
while (!map.containsKey(word)) {
// 如果是单字,退出循环
if (word.length() == 1) {
// 如果是字母或是数字要将连续的字母或者数字分在一起
if (word.matches("[a-zA-z0-9]")) {
// 如果是正向匹配直接循环将后续连续字符加起来
if (leftToRight) {
for (int i = spiltStr.indexOf(word, position) + 1; i < spiltStr
.length(); i++) {
if ((spiltStr.charAt(i) >= '0' && spiltStr
.charAt(i) <= '9')
|| (spiltStr.charAt(i) >= 'A' && spiltStr
.charAt(i) <= 'Z')
|| (spiltStr.charAt(i) >= 'a' && spiltStr
.charAt(i) <= 'z')) {
word += spiltStr.charAt(i);
} else
break;
}
} else {
// 如果是逆向匹配,从当前位置之前的连续数字、字母字符加起来并翻转
for (int i = spiltStr.indexOf(word, position - 1) - 1; i >= 0; i--) {
if ((spiltStr.charAt(i) >= '0' && spiltStr
.charAt(i) <= '9')
|| (spiltStr.charAt(i) >= 'A' && spiltStr
.charAt(i) <= 'Z')
|| (spiltStr.charAt(i) >= 'a' && spiltStr
.charAt(i) <= 'z')) {
word += spiltStr.charAt(i);
if (i == 0) {
StringBuffer sb = new StringBuffer(word);
word = sb.reverse().toString();
}
} else {
// 翻转操作
StringBuffer sb = new StringBuffer(word);
word = sb.reverse().toString();
break;
}
}
}
}
break;
}
// 如果是正向最大匹配,舍去最后一个字符
if (leftToRight)
word = word.substring(0, word.length() - 1);
// 否则舍去第一个字符
else
word = word.substring(1);
}
// 将切分出来的字符串存到指定的表中
if (leftToRight)
leftWords.add(word);
else
rightWords.add(word);
// 已处理字符串增加
length += word.length();
}
// 如果是逆向最大匹配,要把表中的字符串调整为正向
if (!leftToRight) {
for (int i = rightWords.size() - 1; i >= 0; i--) {
leftWords.add(rightWords.get(i));
}
}
// 返回切分结果
return leftWords;
}
/**
* 判断两个集合是否相等
*
* @param list1
* 集合1
* @param list2
* 集合2
* @return 如果相等则返回true,否则为false
*/
public boolean isEqual(List list1, List list2) {
if (list1.isEmpty() && list2.isEmpty())
return false;
if (list1.size() != list2.size())
return false;
for (int i = 0; i < list1.size(); i++) {
if (!list1.get(i).equals(list2.get(i)))
return false;
}
return true;
}
/**
* 判别分词歧义函数
*
* @param inputStr
* 待切分字符串
* @return 分词结果
*/
public List resultWord(String inputStr) {
// 分词结果
List result = new ArrayList();
// “左贪吃蛇”分词结果
List resultLeft = new ArrayList();
// “中贪吃蛇”(分歧部分)分词结果
List resultMiddle = new ArrayList();
// “右贪吃蛇”分词结果
List resultRight = new ArrayList();
// 正向最大匹配分词结果
List left = new ArrayList();
// 逆向最大匹配分词结果
List right = new ArrayList();
left = spilt(inputStr, true);
/*System.out.println("正向分词结果:");
for (String string : left) {
System.out.print(string + "/");
}
System.out.println("\n逆向分词结果:");*/
right = spilt(inputStr, false);
/*for (String string : right) {
System.out.print(string + "/");
}
System.out.println("\n双向分词结果:");*/
// 判断两头的分词拼接,是否已经在输入字符串的中间交汇,只要没有交汇,就不停循环
while (left.get(0).length() + right.get(right.size() - 1).length() < inputStr
.length()) {
// 如果正逆向分词结果相等,那么取正向结果跳出循环
if (isEqual(left, right)) {
resultMiddle = left;
break;
}
// 如果正反向分词结果不同,则取分词数量较少的那个,不用再循环
if (left.size() != right.size()) {
resultMiddle = left.size() < right.size() ? left : right;
break;
}
// 如果以上条件都不符合,那么实行“贪吃蛇”算法
// 让“左贪吃蛇”吃下正向分词结果的第一个词
resultLeft.add(left.get(0));
// 让“右贪吃蛇”吃下逆向分词结果的最后一个词
resultRight.add(right.get(right.size() - 1));
// 去掉被“贪吃蛇”吃掉的词语
inputStr = inputStr.substring(left.get(0).length());
inputStr = inputStr.substring(0,
inputStr.length() - right.get(right.size() - 1).length());
// 清理之前正逆向分词结果,防止造成干扰
left.clear();
right.clear();
// 对没被吃掉的字符串重新开始分词
left = spilt(inputStr, true);
right = spilt(inputStr, false);
}
// 循环结束,说明要么分词没有歧义了,要么"贪吃蛇"从两头吃到中间交汇了
// 如果是在中间交汇,交汇时的分词结果,还要进行以下判断:
// 如果中间交汇有重叠了:
// 正向第一个分词的长度 + 反向最后一个分词的长度 > 输入字符串总长度,就直接取正向的
if (left.get(0).length() + right.get(right.size() - 1).length() > inputStr
.length())
resultMiddle = left;
// 如果中间交汇,刚好吃完,没有重叠:
// 正向第一个分词 + 反向最后一个分词的长度 = 输入字符串总长度,那么正反向一拼即可
if (left.get(0).length() + right.get(right.size() - 1).length() == inputStr
.length()) {
resultMiddle.add(left.get(0));
resultMiddle.add(right.get(right.size() - 1));
}
// 将没有歧义的分词结果添加到最终结果result中
for (String string : resultLeft) {
result.add(string);
}
for (String string : resultMiddle) {
result.add(string);
}
// “右贪吃蛇”存储的分词要调整为正向
for (int i = resultRight.size() - 1; i >= 0; i--) {
result.add(resultRight.get(i));
}
return result;
}
/**
* 将一段话分割成若干句话,分别进行分词
*
* @param inputStr
* 待分割的一段话
* @return 这段话的分词结果
*/
public List resultSpilt(String inputStr) {
// 用于存储最终分词结果
List result = new ArrayList();
// 如果遇到标点就分割成若干句话
String regex = "[,。;!?]";
String[] st = inputStr.split(regex);
// 将每一句话的分词结果添加到最终分词结果中
for (String stri : st) {
List list = resultWord(stri);
result.addAll(list);
}
return result;
}
public static void main(String[] args) {
// example:过来看房价贵不贵?乒乓球拍卖完了
Scanner input = new Scanner(System.in);
String str = input.nextLine();
WordSpilt wordSpilt = null;
try {
wordSpilt = new WordSpilt();
} catch (IOException e) {
e.printStackTrace();
}
List list = wordSpilt.resultWord(str);
for (String string : list) {
System.out.print(string + "/");
}
}
}
其中的src/dict.txt为字典文件,这里设置如下:
欢迎
脚本之家
脚本下载
脚本
教程
素材
下载
运行结果如下:
希望本文所述对大家java程序设计有所帮助。
java 字符串分词_Java实现的双向匹配分词算法示例相关推荐
- java字符串操作_Java的字符串操作
Java的字符串操作 小型送分题:Java有字符串(String),StringBuffer(字符串缓存),StringBuilder(字符串建造者?)各种实现,究其原因还是历史上的各种坑. 一.不同 ...
- java 字符串函数_Java字符串函数– 25+必须知道方法
java 字符串函数 Java字符串函数 (Java String Functions) Java String class has a lot of functions to manipulate ...
- java字符串编程_java字符串抉择
下面我们就字符串连接方面分析. 1.String 打开String的源码,如图所示 会发现存储字符串的字符数值是final常量.再看St final修饰的属性为常量(值不可改变),要么在声明的同时赋值 ...
- java 字符串用法_java中字符串的用法
1.String中的每个字符都是一个16位的Unicode字符,用Unicode很容易表达丰富的国际化字符集,比如很好的中文支持.甚至Java的标识符都可以用汉字,但是没人会用吧(只在一本清华的< ...
- java字符串切割_java字符串常用操作方法(查找、截取、分割)
如下所示: public class 字符串常用操作 { public static void main(String[] args) { /* * 查找子串 */ String str1=" ...
- java字符串定义_java字符串基本概念
java字符串基本概念 那随意了 • 2020 年 02 月 23 日 字符串字符串本质是一个char类型的数组 private final char value[] //底层的实现代码 不可变字符串 ...
- java 字符串面试_Java字符串面试问答
java 字符串面试 String is one of the most widely used Java Class. Here I am listing some important Java S ...
- java字符串去重_java字符串去重方法详解,字符串如何去重?
在Java面试的时候,你一定遇到过java字符串去重这个面试题,那么这个题目你是怎样解答的呢?下面一起来看看相关答案吧. 具体的问题: 输入一串带重复字符的字符串,输出第一次出现的字符的字符串. 例: ...
- java 字符串总结_Java中字符串(String)总结
先说说JDK API: JDK中包含大量的API类库,所谓API(Application Programming Interface,应用程序编程接口,这些功能以类的形式封装). JDK API包含的 ...
最新文章
- 【硬核干货 | 程序的编译、链接、装载与运行】
- 微软发布WP SDK8.0 新增语音、应用内支付等原生API
- Python基本语法_强制数据类型转换
- mysql查询大量数据报错_mysql 查询大量数据报错
- Win8Metro(C#)数字图像处理--2.33图像非线性变换
- 2021曲靖高考成绩查询时间,2021年曲靖高考成绩排名及成绩公布时间什么时候出来...
- 如何删除textarea的移动版Safari的阴影?
- 关于Java的多线程Runnable的个人理解(基础,不讲概念)
- Linux笔记(shell特殊符号,sort排序,wc统计,uniq去重,tee,tr,split)
- JZOJ5197 C
- 猫头鹰的深夜翻译:JAVA中异常处理的最佳实践
- 关于setTimeout的面试题
- 比较好的文章地址收集
- 简单返回顶部代码及注释说明
- C语言实数除法怎样保留小数(编程技巧)
- 浏览器Cookie策略
- 【Python爬虫+js逆向】使用Python爬取腾讯漫画的逆向分析(典型签名验证反爬虫的解决方案)——以腾讯动漫《一人之下》第一话为例
- 接触Firefox的xpi
- 【音频】G711编码原理
- 关于过渡矩阵和坐标变换公式的思考
热门文章
- npm pm2 的安装及常用命令
- 将linux终端的输出信息保存到log中
- 非计算机行业转行后台开发入职字节跳动
- NX二次开发-BlockUI选择面控件设置选择规格face_select0->SetFaceRules(1)及设置单选多选
- Facebook广告投放技巧及思路、如何最大化发挥广告效益!
- 第四届“传智杯”全国大学生IT技能大赛-Java B组题解
- Java求生之路--迷你DVD管理器
- MySQL 查询各个班级语文成绩最高的学生信息
- [转]占用五分钟 受益一辈子
- 一点一点学习C++之笔记001