Java實現編輯距離算法
編輯距離,又稱Levenshtein距離(萊文斯坦距離也叫做Edit Distance),是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數,如果它們的距離越大,說明它們的相似度越小。許可的編輯操作包括將一個字符替換成另一個字符,插入一個字符,刪除一個字符。
oracle數據庫中有一個編輯距離函數: UTL_MATCH.EDIT_DISTANCE(str1,str2)
在plsql中執行: select UTL_MATCH.EDIT_DISTANCE('Java你好','你好') from dual;
執行結果為: 4
此函數的含義為:
計算兩個字符串的差異, str1 str2, str1要做多少次插入 刪除、 替換、 操作才能與str2一致(每次一個字符),這里取出來的是最小變更次數 即 最小距離。
eg: str1 = "Java你好"; str2="你好";
由 str1 變為 str2, 至少需要做四次刪除操作,分別刪除字符 J ,a, v, a然后才能與str2保持一致。故這里的編輯距離為 4。
(這里也可以由 str2變為srt1,str2至少需要做4次插入操作,分別在前面插入 J ,a ,v,a 才能與str1保持一致。)
Java代碼實現編輯距離,且計算出兩個字符串的相似度(這里是因為我要做hive的自定義UDF函數,所以繼承了 UDF,正常使用可以不用繼承UDF類。)
字符串相似度: 相似度 = (長串的長度 - 編輯距離)/ 長串的長度 ; // 長串的長度: Math.max(str1.length(),str2.length());
OracleEditDistance .java
package org.clio.hiveudf.hiveudf; import org.apache.hadoop.hive.ql.exec.UDF; /** * <p> * Description: 編輯距離算法 * 距離值:變更次數--- 先計算兩個字符串的差異, str1 str2, str1要做多少次(每次一個char字符)增加 刪除 替換 操作 才能與str2一致 * 相似度:用最長的字符串的len 減去 變更次數 ,然后除以最長的字符串長度. similarity = (maxLen - changeTimes)/maxLen * ORACLE函數: UTL_MATCH.EDIT_DISTANCE * select UTL_MATCH.EDIT_DISTANCE('Java你好','你好') from dual; * </p> * @author duanfeixia * @date 2019年7月30日 */ public class OracleEditDistance extends UDF{ /** * 編輯距離算法 * @param sourceStr 原字符串 * @param targetStr 目標字符串 * @return 返回最小距離: 原字符串需要變更多少次才能與目標字符串一致(變更動作:增加/刪除/替換,每次都是以字節為單位) */ public static int minDistance(String sourceStr, String targetStr){ int sourceLen = sourceStr.length(); int targetLen = targetStr.length(); if(sourceLen == 0){ return targetLen; } if(targetLen == 0){ return sourceLen; } //定義矩陣(二維數組) int[][] arr = new int[sourceLen+1][targetLen+1]; for(int i=0; i < sourceLen+1; i++){ arr[i][0] = i; } for(int j=0; j < targetLen+1; j++){ arr[0][j] = j; } Character sourceChar = null; Character targetChar = null; for(int i=1; i < sourceLen+1 ; i++){ sourceChar = sourceStr.charAt(i-1); for(int j=1; j < targetLen+1 ; j++){ targetChar = targetStr.charAt(j-1); if(sourceChar.equals(targetChar)){ /* * 如果source[i] 等於target[j],則:d[i, j] = d[i-1, j-1] + 0 (遞推式 1) */ arr[i][j] = arr[i-1][j-1]; }else{ /* 如果source[i] 不等於target[j],則根據插入、刪除和替換三個策略,分別計算出使用三種策略得到的編輯距離,然后取最小的一個: d[i, j] = min(d[i, j - 1] + 1, d[i - 1, j] + 1, d[i - 1, j - 1] + 1 ) (遞推式 2) >> d[i, j - 1] + 1 表示對source[i]執行插入操作后計算最小編輯距離 >> d[i - 1, j] + 1 表示對source[i]執行刪除操作后計算最小編輯距離 >> d[i - 1, j - 1] + 1表示對source[i]替換成target[i]操作后計算最小編輯距離 */ arr[i][j] = (Math.min(Math.min(arr[i-1][j], arr[i][j-1]), arr[i-1][j-1])) + 1; } } } System.out.println("----------矩陣打印---------------"); //矩陣打印 for(int i=0;i<sourceLen+1;i++){ for(int j=0;j<targetLen+1;j++){ System.out.print(arr[i][j]+"\t"); } System.out.println(); } System.out.println("----------矩陣打印---------------"); return arr[sourceLen][targetLen]; } /** * 計算字符串相似度 * similarity = (maxlen - distance) / maxlen * ps: 數據定義為double類型,如果為int類型 相除后結果為0(只保留整數位) * @param str1 * @param str2 * @return */ public static double getsimilarity(String str1,String str2){ double distance = minDistance(str1,str2); double maxlen = Math.max(str1.length(),str2.length()); double res = (maxlen - distance)/maxlen; //System.out.println("distance="+distance); //System.out.println("maxlen:"+maxlen); //System.out.println("(maxlen - distance):"+(maxlen - distance)); return res; } public static String evaluate(String str1,String str2) { double result = getsimilarity(str1,str2); return String.valueOf(result); } public static void main(String[] args) { String str1 = "2/F20NGNT"; String str2 = "1/F205ONGNT"; int result = minDistance(str1, str2); String res = evaluate(str1,str2); System.out.println("最小編輯距離minDistance:"+result); System.out.println(str1+"與"+str2+"相似度為:"+res); } }
執行結果如下圖:
編輯距離主要是為了計算兩個字符串的相似度,在查閱資料的時候看到了一篇博客關中文的相似度匹配算法覺得挺有意思的,這里引入了一個 音形碼 的概念來計算漢字的相似度,感覺興趣的可以看看原博主的博客。
中文相似度匹配算法: https://blog.csdn.net/chndata/article/details/41114771
音形碼
音形碼示例:
這里順便記錄一下mysql函數自動填充時間工具表。
因為hive對數據初始化時每次都要手動更新表 rs_tool_date,往里面插入日期,讓里面的數據為 20190101到當前日期前一天為止。(所以就寫了這個函數,需要的時候手動運行一下這個函數即可~)
BEGIN declare i int default 0; declare len int default 1; set i=1; -- 從i=1開始,保證存儲在數據庫中的數據為20190101到當前日期的前一天 set len = DATEDIFF(now(),date'2019-01-01'); -- set len = DATEDIFF(date('2019-07-15'),date'2019-01-01'); DROP TABLE IF EXISTS `rs_tool_date`; -- rs_tool_date CREATE TABLE `rs_tool_date` ( `ID` bigint(20) NOT NULL AUTO_INCREMENT, `tool_date` date DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT; while i<= len DO insert into rs_tool_date (tool_date) values(DATE_ADD(now(),INTERVAL -i day)); set i=i+1; end while; END