public class DfaAndKmp {
/**
* DFA查询
* @param document 待查询文档
* @param KeyWords 关键词集合
* @param matchType 匹配规则:0最小规则,1最大规则
* @return map(key为匹配关键词,value匹配次数)
*/
public static Map<String, Integer> dfaSearch(String document, Set<String> KeyWords, int matchType) {
// 匹配结果, key为匹配关键词,value匹配次数
Map<String, Integer> matchRes = new HashMap<>();
// 构造关键字Map
Map<String, Object> wordMap = createWordMap(KeyWords);
// 当前处理关键词
String curWord = "";
// 当前使用关键词map
Map<String, Object> curWordMap = wordMap;
// 当前正在匹配的关键词首字符序号
int n = 0;
for (int i = 0; i < document.length(); i++) {
String c = String.valueOf(document.charAt(i));
Object curObj = curWordMap.get(c);
// 关键词集合中不存在当前字符,当前字符匹配失败,重新开始匹配文档中下一个字符
if (null == curObj) {
// 重新使用初始关键词map进行匹配
curWordMap = wordMap;
// 当前处理关键词重置为空
curWord = "";
i = n++;
continue;
}
// 当前字符匹配成功,添加到匹配关键词中
curWord += document.charAt(i);
// 当前字符对应关键词map
Map<String, Object> curMap = (Map<String, Object>) curObj;
if (curMap.size() == 0) { // map为空代表当前字符为最后一个,匹配成功
// 保存匹配记录
Integer value = matchRes.putIfAbsent(curWord, 1);
if (null != value) {
matchRes.put(curWord, ++value);
}
// 最小匹配则直接返回结果
if (matchType == 0) {
return matchRes;
}
// 重新使用初始关键词map进行匹配
curWordMap = wordMap;
// 当前处理关键词重置为空
curWord = "";
// 开始下一个字符的匹配
i = n++;
} else { // map非空,则使用当前字符对应关键词map继续进行匹配
curWordMap = curMap;
}
}
return matchRes;
}
/**
* 利用kmp算法查找字符匹配次数
* @param document 查询文档
* @param keyWord 关键词(匹配字符串)
* @param matchType 匹配规则:0最小规则,1最大规则
* @return
*/
public static int kmpSearch(String document, String keyWord, int matchType) {
// 成功匹配此时
int matchCount = 0;
// 被匹配字符串字符序号
int curNext = 0;
// 匹配字符串字符序号
// 关键词部分匹配值
int keyWorkKmpValue = getKmpValue(keyWord);
int j = 0;
for (int i = 0; i < document.length();) {
char textChar = document.charAt(i);
char keyWordChar = keyWord.charAt(j);
if (textChar == keyWordChar) { //当前字符匹配成功
//关键词完全匹配
if (j == keyWord.length() - 1) {
// 匹配次数加1
matchCount ++;
if (matchType == 0) { // 最小匹配则直接返回
return matchCount;
} else { //最大匹配则使用关键词的kmp部分匹配值进行移动,然后继续匹配
curNext += (keyWord.length() - keyWorkKmpValue);
i = curNext + keyWorkKmpValue;
j = keyWorkKmpValue;
continue;
}
}
// 继续匹配下一个字符
j++;
i++;
continue;
} else { //未匹配成功
// 首字符不匹配,则重新开始下一字符的匹配
if (j == 0) {
curNext++;
i = curNext;
j = 0;
continue;
}
// 使用kmp部分匹配值进行移动,然后重新开始匹配
int kmpValue = getKmpValue(keyWord.substring(0, j));
curNext += (j - kmpValue);
i = curNext + kmpValue;
j = kmpValue;
}
}
return matchCount;
}
/**
* 构造关键字Map
*/
private static Map<String, Object> createWordMap(Set<String> KeyWords) {
Map<String, Object> keyWordsMap = new HashMap<>(KeyWords.size());
// 遍历关键词
KeyWords.stream().forEach(keyWord -> {
// 当前操作map
Map<String, Object> curMap = keyWordsMap;
// 遍历关键词每个字符
for (int i = 0; i < keyWord.length(); i++) {
String keyChar = String.valueOf(keyWord.charAt(i));
if (curMap.get(keyChar) == null) { // 字符所属map为空则初始化
Map<String, Object> chaMap = new HashMap<>();
curMap.put(keyChar, chaMap);
curMap = chaMap;
} else { //非空则操作当前字符所属map
curMap = (Map<String, Object>) curMap.get(keyChar);
}
// 最后一个字符,设置为空map
if (i == keyWord.length() - 1) {
curMap = new HashMap<>(0);
}
}
});
return keyWordsMap;
}
/**
* 获取kmp部分匹配值
* @return
*/
private static int getKmpValue(String partMatch) {
Objects.requireNonNull(partMatch);
if (partMatch.length() <= 1) {
return 0;
}
// 前缀集合(除了最后一个字符以外,一个字符串的全部头部组合)
Set<String> prefix = new HashSet<>();
// 后缀集合(指除了第一个字符以外,一个字符串的全部尾部组合)
Set<String> suffix = new HashSet<>();
// 获取前缀集合、后缀集合
int length = partMatch.length();
for (int i = 0; i < length; i++) {
for (int j = 1; j < length; j++) {
prefix.add(partMatch.substring(0, j));
suffix.add(partMatch.substring(j, length));
}
}
// 获取部分匹配值("前缀"和"后缀"的最长共有元素的长度)
prefix.retainAll(suffix);
if (prefix.size() <= 0) {
return 0;
}
int value = prefix.stream()
.max(Comparator.comparingInt(String::length))
.get()
.length();
return value;
}
public static void main(String[] args) {
String[] keyWorks = {"电影","影视节", "电吉他开始做"};
String document = "此次电吉他开始,电吉他开始做电影视节日计费电影视节";
Map<String, Integer> stringIntegerMap = dfaSearch(document, new HashSet<>(Arrays.asList(keyWorks)), 1);
System.out.println("DFA匹配结果:" + stringIntegerMap);
String kmpTest = "abcdabfabcdabcdabdeabcdabdabc";
int count = kmpSearch(kmpTest, "abcdabdeee", 1);
System.out.println("kmp匹配次数:" + count);
}
}