今天写了下余弦相似度计算的算法,之前在学校做项目的时候使用到了,一直没去整理。
所谓的字符串余弦相似度,就是把每个字符串比作一个向量,通过计算向量余弦值来判断字符串的相似程度,余弦值越接近1,说明两个字符串的相似度就越高,余弦值的计算公式为:(向量a*向量b)/(向量a的模*向量b的模)
实现如下:
package demo.similarity;
import java.util.HashMap;
import java.util.Map;
public class Utils {
public static double calculateSimilarity(String source, String dest){
//用map存放词在字符串中所出现的次数,key为词,value为整型数组
Map<String, int[]> wordAppearTimes = new HashMap<String, int[]>();
//这里是按照单个字来进行分割,假如在这里做一次分词,效果会更好点,但是复杂度也会高些
//生成source字符串的向量
int sourceLen = source.length();
for(int i=0; i<sourceLen; ++i){
if(wordAppearTimes.containsKey(source.charAt(i)+"")){
++(wordAppearTimes.get(source.charAt(i)+"")[0]);
}else{
int[] appearTimes = new int[2];
appearTimes[0] = 1;
appearTimes[1] = 0;
wordAppearTimes.put(source.charAt(i)+"", appearTimes);
}
}
//生成dest字符串的向量
int destLen = dest.length();
for(int i=0; i<destLen; ++i){
if(wordAppearTimes.containsKey(dest.charAt(i)+"")){
++(wordAppearTimes.get(dest.charAt(i)+"")[1]);
}else{
int[] appearTimes = new int[2];
appearTimes[0] = 0;
appearTimes[1] = 1;
wordAppearTimes.put(dest.charAt(i)+"", appearTimes);
}
}
//向量source的模
double sourceModel = 0.00;
//向量dest的模
double destModel = 0.00;
//向量积
double crossProduct = 0.00;
for(Map.Entry<String, int[]> entry : wordAppearTimes.entrySet()){
sourceModel += entry.getValue()[0] * entry.getValue()[0];
destModel += entry.getValue()[1] * entry.getValue()[1];
crossProduct += entry.getValue()[0] * entry.getValue()[1];
}
sourceModel = Math.sqrt(sourceModel);
destModel = Math.sqrt(destModel);
double similarity = crossProduct / (sourceModel * destModel);
return similarity;
}
public static void main(String[] args) {
String s1 = "代码整洁之道";
String s2 = "代码整洁之道";
System.out.println(calculateSimilarity(s1,s2));
}
}
输出为:
1.000000000000000
虽然这样能计算两个字符串的相似度,但是也存在一些缺陷:
1.按照上面的方法计算相似度,代码并没有按照词出现的顺序计算,比如我把上面的“代码整洁之道”改为“代码之道整洁”,结果还会是一样的
2.在实际应用中,按照单字来计算余弦相似度也是比较少的,除非系统对相似度计算得要求不高;对字符串加入分词,效果会更好一些