我是靠谱客的博主 完美长颈鹿,最近开发中收集的这篇文章主要介绍利用simhash算法原理和HanLP分词原理计算文本相似度工具类前言一、计算文本相似度工具类SimHashUtils总结,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
前言
该方法是采用HanLP分词原理
参考大佬们的代码与原理:(部分)
simhash算法及原理简介
海量文本用 Simhash, 2小时变4秒! | 文本分析:大规模文本处理(2)
一、计算文本相似度工具类SimHashUtils
package com.siboo.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.math.BigInteger;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.corpus.tag.Nature;
import com.hankcs.hanlp.seg.common.Term;
import com.siboo.utils.StringUtils;
/**
*
* @Title: SimHashUtils.java
* @Package com.newtec.knowGraph.data.params
* @author 陈笑璞
* @date 2020年6月22日 下午5:35:32
* @Description: 计算文本相似度工具类(采用SimHash算法思想)
*
*/
public class SimHashUtils {
private static int hashbits = 64;// 默认64位,即将一个文本转换为64bit数据
private static Pattern CHINES_PATTERIN = Pattern.compile("^[u4e80-u9fa5]+$" );
private static final int DWEIGHT = 1;// 默认权重
public SimHashUtils() {
super();
}
public SimHashUtils(int hashbits) {
super();
this.hashbits = hashbits;
}
/**
*
* @Title: splitFourEqual
* @Description: 将Simhash签名值平均分割为4等份
* @param signature 字符串
* @return List<String> 返回分割的4等份字符串
*/
public static List<String> splitFourEqual(String signature) {
int length = signature.length();
int m = 4;
int num = length / m;
List<String> list = new ArrayList<String>();
for (int i = 0; i < m; i++) {
list.add(signature.substring(i*num, (i+1)*num));
}
return list;
}
/**
*
* @Title: ClobToString
* @Description: Java读取Oracle的CLOB字段转换为String数据
* @param clob CLOB字段类型数据
* @throws SQLException
* @throws IOException
* @return String 返回转换为String数据
*/
public static String ClobToString(Clob clob) throws SQLException, IOException {
Reader is = clob.getCharacterStream();// 得到流
BufferedReader br = new BufferedReader(is);
String s = br.readLine();
StringBuffer sb = new StringBuffer();
while (s != null) {
// 执行循环将字符串全部取出付值给StringBuffer由StringBuffer转成String
sb.append(s);
s = br.readLine();
}
if(br!=null){
br.close();
}
if(is!=null){
is.close();
}
return sb.toString();
}
/**
*
* @Title: getSimhashSignatureValue
* @Description: 计算SimHash签名值
* @return String 返回hashbits位二进制字符串的SimHash签名值
*/
public static String getSimhashSignatureValue(String text) {
// 1、分词
List<Term> terms = HanLP.segment(text);
List<String> list = new ArrayList<String>();
for (Term term : terms) {
// 2.权重
if ("w".equals(StringUtils.toString(term.nature))) {// 排除停用词性为w:标点
continue;
}
Integer num = calculateWeight(term);
// 3.hash值和加权
list.add(getStrSimHash(term.word, num));
}
// 4.合并
int[] merge = new int[hashbits];
for (String str : list) {
String[] split = str.split(" ");
for (int i = 0, len = split.length; i < len; i++) {
merge[i] += StringUtils.toInt(split[i], 0);
}
}
// 5.降维
StringBuilder rd = new StringBuilder();
for (int i : merge) {// 对于n-bit签名的累加结果,如果大于0则置1,否则置0,从而得到该语句的simhash值
if (i > 0) {
rd.append("1");
} else {
rd.append("0");
}
}
return rd.toString();
}
/**
*
* @Title: calculateWeight
* @Description: 计算分词权重
* @param term
* @return int 返回分词权重
*/
public static int calculateWeight(Term term) {
// 汉字数
int num = countChinese(term.word);
// 大于3个汉字,权重增加
int value = num >= 3 ? 2 + (num - 3) / 2 : DWEIGHT;
// 专属词,如果有两个字至少要最小分是2分
if (term.nature == Nature.nz && value <= DWEIGHT) {
value = DWEIGHT + 1;
}
//
System.out.println("value(权重)===>>>" + value);
return value>5?5:value;
}
/**
*
* @Title: countChinese
* @Description: 统计汉字数
* @param text 字符串
* @return int 返回汉字数
*/
public static int countChinese(String text) {
if (StringUtils.isNull(text)) return 0;
int amount = 0;
char[] chs = text.toCharArray();
for (char c : chs) {
amount = CHINES_PATTERIN.matcher(StringUtils.toString(c)).matches() ? ++ amount : amount;
}
return amount;
}
/**
*
* @Title: getStrSimHash
* @Description: 加权处理方法
* @param tokens 每个分词文本
* @param weight 权重
* @return String 返回加权处理后的hashbits位的二进制字符串
*/
public static String getStrSimHash(String tokens, Integer weight) {
int[] v =new int[hashbits];
StringTokenizer stringTokens =new StringTokenizer(tokens);
while (stringTokens.hasMoreTokens()) {
String temp = stringTokens.nextToken();
BigInteger t = hash(temp);
for (int i =0; i<v.length;i++) {
BigInteger bitmask =new BigInteger("1").shiftLeft(i);
if (t.and(bitmask).signum() !=0) {
v[i] +=1;
}else {
v[i] -=1;
}
}
}
// 加权处理
// 每一个分词的 hashcode 中,对应位上如果为1,则该位加上权重w,这里权重为1,即+1,;对应位上如果不为1,则该位减去权重w,这里即-1
StringBuffer simHashBuffer =new StringBuffer();
for (int i =0; i<v.length;i++) {
if (v[i] >=0) {
simHashBuffer.append(1 * weight).append(" ");
}else{
simHashBuffer.append(-1 * weight).append(" ");
}
}
return simHashBuffer.toString().trim();
}
/**
*
* @Title: hash
* @Description: 对单个的分词进行hash计算
* @param source 分词
* @return BigInteger 返回分词对应的hash值
*/
private static BigInteger hash(String source) {
if (source == null || source.length() == 0) {
return new BigInteger("0");
}
/**
* 当sourece 的长度过短,会导致hash算法失效,因此需要对过短的词补偿
*/
//
while (source.length() < 3) {
//
source = source + source.charAt(0);
//
}
char[] sourceArray = source.toCharArray();
BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7);
BigInteger m = new BigInteger("1000003");
BigInteger mask = new BigInteger("2").pow(hashbits).subtract(new BigInteger("1"));
for (char item : sourceArray) {
BigInteger temp = BigInteger.valueOf((long) item);
x = x.multiply(m).xor(temp).and(mask);
}
x = x.xor(new BigInteger(String.valueOf(source.length())));
if (x.equals(new BigInteger("-1"))) {
x = new BigInteger("-2");
}
return x;
}
/**
*
* @Title: getDistance
* @Description: 求两个二进制字符串之间的海明距离
* @param str1 SimHash签名值(64位二进制字符串)
* @param str2 SimHash签名值(64位二进制字符串)
* @return int 返回海明距离值(海明距离在3以内的可认为相似度比较高)
*/
public int getDistance(String str1, String str2) {
int distance;
if (str1.length() != str2.length()) {
distance = -1;
} else {
distance = 0;
for (int i = 0; i < str1.length(); i++) {
if (str1.charAt(i) != str2.charAt(i)) {
distance++;
}
}
}
return distance;
}
/**
*
* @Title: comparisonHammingDistance
* @Description: 比较海明距离
* @param str1 SimHash签名值(64位二进制字符串)
* @param str2 SimHash签名值(64位二进制字符串)
* @return int 返回海明距离值(海明距离在3以内的可认为相似度比较高)
*/
public static int comparisonHammingDistance(String str1, String str2) {
if (StringUtils.isNull(str1)||StringUtils.isNull(str2)) {
return -1;
}
int distance = 0;
char[] chs1 = str1.toCharArray();
char[] chs2 = str2.toCharArray();
int len = Math.min(chs1.length, chs2.length);// 计算最小值,防止遍历数组下标溢出
for (int i = 0; i < len; i++) {// 遍历长度有待深究
if ((chs1[i] ^ chs2[i]) == 1) {// 异或判断
distance ++;
}
}
//
System.err.println("海明距离===>>>" + distance);
return distance;
}
/**
*
* @Title: getWeightToPlaceName
* @Description: 根据文本计算出地名和出现的次数
* @param text 一段文本
* @return Map<String,Integer> 返回{地名:出现次数}
*/
public static Map<String, Integer> getWeightToPlaceName(String... text) {
Map<String, Integer> placeNameAndWeightMap = new HashMap<String, Integer>();
for (String str : text) {
List<Term> terms = HanLP.segment(str);
for (Term term : terms) {
// 2.权重
if ("ns".equals(StringUtils.toString(term.nature))) {// ns:地名
placeNameAndWeightMap.compute(term.word, (k, v) -> {
if (v == null) {
v = 1;
} else {
v += 1;
}
return v;
});
}
}
}
return sortMapByValue(placeNameAndWeightMap);
}
/**
*
* @Title: sortMapByValue
* @Description: 按值排序
* @param oriMap 需要排序的对象
* @return Map<String,Integer> 返回排序后的对象
*/
public static Map<String, Integer> sortMapByValue(Map<String, Integer> oriMap) {
Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
if (oriMap != null && !oriMap.isEmpty()) {
List<Map.Entry<String, Integer>> entryList = new ArrayList<Map.Entry<String, Integer>>(oriMap.entrySet());
Collections.sort(entryList,new Comparator<Map.Entry<String, Integer>>() {
public int compare(Entry<String, Integer> entry1,Entry<String, Integer> entry2) {
return entry2.getValue() - entry1.getValue();
}
});
Iterator<Map.Entry<String, Integer>> iter = entryList.iterator();
Map.Entry<String, Integer> tmpEntry = null;
while (iter.hasNext()) {
tmpEntry = iter.next();
sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
}
}
return sortedMap;
}
}
调用方法如下:
public static void main(String[] args) {
String s1 = "劳斯莱斯女神n" +
"n" +
"这个车标的设计者是英国画家兼雕刻家查尔斯·赛克斯。20世纪初,经朋友蒙塔古邀请,赛克斯负责为劳斯莱斯设计一尊雕塑车标。当时,已婚的蒙塔古疯狂地爱着他的女秘书桑顿,恳请赛克斯以桑顿为原型设计车标。所以,赛克斯的最初设计中,雕像是一尊披着长袍的女人将手指放在嘴唇上,象征着蒙塔古与桑顿之间不能说的秘密情史。这个恋爱故事历经重重磨难,桑顿身份地位曾是脱衣舞女郎,所以两人根本无法在一起生活,在得到家庭与蒙塔古妻子的谅解后,两人最终可以走到一起,不幸的是,后来桑顿在一次乘船旅行中不幸遭遇德军水雷,永远沉入了冰冷的大海。n" +
"n" +
"后来,他们这段美好的爱情又略带凄惨故事就保留在了这个车标上,罗 -罗二人也是蒙塔古的好友,他们得知这件事之后非常感动。后来,他们邀请赛克斯又把它改为双手如羽翼般向后伸展的形象,也就是今天的“飞天女神”。 1911年,它正式成为劳斯莱斯车的车标。从此,劳斯莱斯的飞天女神车标更是美丽的爱情象征了!";
String s2 = "劳斯莱斯女神n" +
"n" +
"这个车标的设计者是英国画家兼雕刻家查尔斯·赛克斯。当时,已婚的蒙塔古疯狂地爱着他的女秘书桑顿,恳请赛克斯以桑顿为原型设计车标。所以,赛克斯的最初设计中,雕像是一尊披着长袍的女人将手指放在嘴唇上,象征着蒙塔古与桑顿之间不能说的秘密情史。这个恋爱故事历经重重磨难,桑顿身份地位曾是脱衣舞女郎,所以两人根本无法在一起生活,在得到家庭与蒙塔古妻子的谅解后,两人最终可以走到一起,不幸的是,后来桑顿在一次乘船旅行中不幸遭遇德军水雷,永远沉入了冰冷的大海。n" +
"n" +
"后来,他们这段美好的爱情又略带凄惨故事就保留在了这个车标上,他们得知这件事之后非常感动。后来,他们邀请赛克斯又把它改为双手如羽翼般向后伸展的形象,也就是今天的“飞天女神”。 1911年,它正式成为劳斯莱斯车的车标。从此,劳斯莱斯的飞天女神车标更是美丽的爱情象征了!";
long l1 = System.currentTimeMillis();
System.out.println("海明距离===>>>"+SimHashUtils.comparisonHammingDistance(SimHashUtils.getSimhashSignatureValue(s1),SimHashUtils.getSimhashSignatureValue(s2)));
long l2 = System.currentTimeMillis();
System.out.println("总共耗时:"+(l2-l1)+"毫秒");
}
总结
该工具类是博主根据网上了解其他博主的见解与算法原理,再结合自己的理解整出来的,应该还有些不足的地方,还有就是权重那块设定也不够合理,不过暂时没有更好找到更好的方法,就先这样处理了,同时也欢迎各路大神过来一起探讨哈。
最后
以上就是完美长颈鹿为你收集整理的利用simhash算法原理和HanLP分词原理计算文本相似度工具类前言一、计算文本相似度工具类SimHashUtils总结的全部内容,希望文章能够帮你解决利用simhash算法原理和HanLP分词原理计算文本相似度工具类前言一、计算文本相似度工具类SimHashUtils总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复