需求:比较两个地址相似程度
1, 排除数字, 排除字母(大小写), 特殊符号
如以下三个地址都可以认为是实际相同,只是表述不同:
湖北省-武汉市-东西湖区 湖北省武汉市东西湖区革新大道四明路物流园c5
湖北省-武汉市-东西湖区 革新大道四明路物流园c5门
湖北省-武汉市-东西湖区 革新大道四明路南特1号
实际上,这些地址,前两个是相同的, 第三个是不同的
思路:
1, 格式化地址, 重复的去掉
2, 去掉数字, 排除字母(大小写), 特殊符号
3, 选取算法
目前能处理的字符串相同的算法很多,常见的:
1)SimHash:算法的主要思想是降维,将高维的特征向量映射成一个f-bit的指纹(fingerprint),通过比较两篇文章的f-bit指纹的Hamming Distance来确定文章是否重复或者高度近似。由于每篇文章我们都可以事先计算好Hamming Distance来保存,到时候直接通过Hamming Distance来计算,所以速度非常快适合大数据计算,google用的就是这个。但是SimHash对于短文本误判率比较高,因此建议大于500字以上的使用此算法。
2)汉明距离: 以理查德·卫斯里·汉明的名字命名的。在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。例如:
1011101 与 1001001 之间的汉明距离是 2。
2143896 与 2233796 之间的汉明距离是 3。
"toned" 与 "roses" 之间的汉明距离是 3。
3)Levenshtein 距离: 又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。
许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。
java中也有实现的工具类:org.apache.commons.lang.StringUtils.getLevenshteinDistance(str1, str2);
可用于:DNA分析,拼字检查,语音辨识,抄袭侦测
4)余弦定理:通过对两个文本分词,TF-IDF算法向量化,对比两者的余弦夹角,夹角越小相似度越高,但由于有可能一个文章的特征向量词特别多导致整个向量维度很高,使得计算的代价太大不适合大数据量的计算。
经过一番测试后, 选择余弦定理
具体实例入下:
未采用分词
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class CosineSimilarAlgorithm {
public static double getSimilarity(String content1, String content2) {
Map<Integer, int[]> wordMap = new HashMap<Integer, int[]>();
// 将两个字符串中的中文字符以及出现的总数封装到,AlgorithmMap中
char[] char1 = content1.toCharArray();
for (int i = 0; i < char1.length; i++) {
if(isHanZi(char1[i])){
int charIndex = getGB2312Id(char1[i]);
if(charIndex != -1){
int[] fq = wordMap.get(charIndex);
if(fq != null && fq.length == 2){
fq[0]++;
}else {
fq = new int[2];
fq[0] = 1;
fq[1] = 0;
wordMap.put(charIndex, fq);
}
}
}
}
char[] char2 = content2.toCharArray();
for (int i = 0; i < char2.length; i++) {
if(isHanZi(char2[i])){
int charIndex = getGB2312Id(char2[i]);
if(charIndex != -1){
int[] fq = wordMap.get(charIndex);
if(fq != null && fq.length == 2){
fq[1]++;
}else {
fq = new int[2];
fq[0] = 0;
fq[1] = 1;
wordMap.put(charIndex, fq);
}
}
}
}
Iterator<Integer> iterator = wordMap.keySet().iterator();
double sqdoc1 = 0;
double sqdoc2 = 0;
double denominator = 0;
while(iterator.hasNext()){
int[] c = wordMap.get(iterator.next());
denominator += c[0]*c[1];
sqdoc1 += c[0]*c[0];
sqdoc2 += c[1]*c[1];
}
return denominator / Math.sqrt(sqdoc1*sqdoc2);
}
public static boolean isHanZi(char ch) {
// 判断是否汉字
return (ch >= 0x4E00 && ch <= 0x9FA5);
}
/**
* 根据输入的Unicode字符,获取它的GB2312编码或者ascii编码,
*
* @param ch 输入的GB2312中文字符或者ASCII字符(128个)
* @return ch在GB2312中的位置,-1表示该字符不认识
*/
public static short getGB2312Id(char ch) {
try {
byte[] buffer = Character.toString(ch).getBytes("GB2312");
if (buffer.length != 2) {
// 正常情况下buffer应该是两个字节,否则说明ch不属于GB2312编码,故返回'?',此时说明不认识该字符
return -1;
}
int b0 = (int) (buffer[0] & 0x0FF) - 161; // 编码从A1开始,因此减去0xA1=161
int b1 = (int) (buffer[1] & 0x0FF) - 161; // 第一个字符和最后一个字符没有汉字,因此每个区只收16*6-2=94个汉字
return (short) (b0 * 94 + b1);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return -1;
}
public static void main(String[] args) {
String address1 = "湖北省-武汉市-东西湖区 革新大道四明路物流园c5门";
String address2 = "湖北省-武汉市-东西湖区 革新大道四明路南特1号";
System.out.println(getSimilarity(address1, address2));
}
}
方案一:
1, 规整地址后(去掉重复省市, 特殊符号,数字,字母)
2, 结合上面的代码
方案二:
1, 规整地址后(去掉重复省市, 特殊符号,数字,字母)
2, 结合上面的代码(加入分词概念), 地址词库的完整直接关系到判断的准确性.
对比方案一和方案二选取最优的一个.
看官若有好的建议,欢迎拍砖