###英文文章分词及已知单词位置计算单词在文章中起始下标
####背景
1.由于最近项目中需要,要实现类似文章跟读的效果,但已知的只有每个单词在文章中的位置下标(即每个单词在文章中是在第几个单词),那么要实现跟读效果就必须根据每个单词在文章中的位置计算出每个单词在整个文章中的具体下标。比如:“My name is Tom.”,我们只知道“My” 、“name”、“is”、“Tom”在文章中分别是第0、1、2、3个单词,而要实现跟读则必须计算出每个词具体的起始下标,如“My” 、“name”、“is”、“Tom”每个词在文章中的起始下标分别为0、3、8、11。这样才能用spanableString实现跟读效果(用法在下一篇博客讲,这里只关注分词相关)
####实现
1.分词
- 首先将分别信息进行分词。分词的步骤是先将文本中的多余空格和换行符、制表符等去掉,只保留每个单词(带符号)之间的一个空格,然后根据空格将文本进行分词。(当然项目中的文章资源需要配合,保证每个词/词+符号 间有空格)这里故意保留符号,符号是算在前一个词里面的,因为跟读标记需要把标点也标记进去。
public String[] divideToWords(String text) {
String[] words;
String str = text.replaceAll("\\s+|\t|\n|\r" +
"|\n\n", " ");
str = str.trim(); //去掉收尾空格
words = str.split(" ");
return words;
}
2.计算每个单词在文章中的下标
- 实现过程需要注意的点:某个单词可能在文章中出现多次;某些单词是缩写的形式,中间可能存在一些字符而非纯字母组成(I’m、don’t等)。
- 实现步骤:先计算每个词在文章中出现次数,用hashMap存储,key为单词,value为出现次数,同时用另一个hashMap存储第n个单词的信息(只要关注第n个单词在文章中是第几次出现);然后遍历每个单词,计算每个单词在文章中的开始下标。
- 匹配单词的规则是前面是非英文字符,后面是空格、回车等(文章最后如果没有,要先补上,否则最后一个可能匹配不上);用正则匹配时要注意先转义* ?等特殊字符,因为这些字符在正则表达式中有特殊的意义和作用。
public HashMap<Integer, WordEntity> calculateTextStartIndex(String[] words, String text) {
wordCountMap.clear();
wordEntitiesMap.clear();
for (int i = 0; i < words.length; i++) {
AppsLog.i(TAG, "单词" + i + " : " + words[i]);
int repeatCount = 1;
if (wordCountMap.get(words[i]) != null) {
repeatCount = wordCountMap.get(words[i]) + 1;
}
wordCountMap.put(words[i], repeatCount); //记录单词出现次数(第几次出现)
WordEntity entity = new WordEntity();
entity.setUnit_index(i);
entity.setWord(words[i]);
entity.setRepeatCount(repeatCount); //第几次出现
wordEntitiesMap.put(i, entity); //记录单词信息
}
//循环遍历查找每个单词的下标
for (int i = 0; i < wordEntitiesMap.size(); i++) {
WordEntity entity = wordEntitiesMap.get(i);
int repeatCount = entity.getRepeatCount();
String word = entity.getWord();
int totalIndex = -1;
String temp = text;
for (int j = 0; j < repeatCount; j++) {
int index = -1;
if (i == 0) {
//如果是第一个单词,直接用indexOf
index = temp.indexOf(word);
if (index != -1) {
AppsLog.i(TAG, "Found1 : " + word + " at " + index);
}
} else {
//非第一个单词,需要用正则不断匹配取得下标,同时得跳过前面已经重复的单词(检测要匹配的单词,单词的前面必须是空格或者标点符号)
String patternWord = word.replace(".", "\\.") //转义替换正则的特殊字符
.replace("?", "\\?")
.replace("*", "\\*")
.replace("+", "\\+")
.replace("^", "\\^")
.replace("(", "\\(")
.replace(")", "\\)")
.replace("$", "\\$")
.replace("[", "\\[")
.replace("]", "\\]")
.replace("{", "\\{")
.replace("}", "\\}")
.replace("|", "\\|");
//.replace("\\", "\\\\")
//前面必须非英文字母,后面为空格或回车等(I'm)
Pattern p = Pattern.compile("[^\\w]" + patternWord + "[\\s\\r\\n]");
Matcher m = p.matcher(temp);
if (m.find()) {
index = m.start() + 1; //起始下标
AppsLog.i(TAG, "Found2 : " + word + " at " + index);
}
}
if (index != -1) { //找到
temp = temp.substring(index + word.length()); //去掉已匹配过的前面的文本
if (totalIndex == -1) {
totalIndex = 0;
}
totalIndex += (index + word.length()); //算几次index的总和加词的长度
if (totalIndex != 0) {
if (j == (repeatCount - 1)) { //最后一次匹配,index减去单词长度
totalIndex -= word.length();
}
}
} else {
if (totalIndex == -1) {
totalIndex = 0;
}
if (totalIndex != 0) {
if (j == (repeatCount - 1)) {
totalIndex -= word.length();
}
}
}
}
if (totalIndex != -1) {
entity.setStartPos(totalIndex);
}
wordEntitiesMap.put(i, entity);
}
for (int i = 0; i < wordEntitiesMap.size(); i++) {
AppsLog.i(TAG, "" + i + " : " + wordEntitiesMap.get(i));
}
return wordEntitiesMap;
}