完整源碼在個人github上 https://github.com/NashLegend/QuicKid java
接上篇,下面是幾個匹配算法的詳情:git
1.徹底匹配github
徹底匹配很簡單了,只要判斷string是否相等就好了。這裏要判斷全部拼音和全部號碼。若是拼音已經符合,就再也不判斷號碼。反正是一我的……
算法
private ScoreAndHits completeMatch(String reg) { ScoreAndHits scoreAndHits = new ScoreAndHits(-1, 0f, new ArrayList<PointPair>()); for (int i = 0; i < fullNameNumberWithoutSpace.size(); i++) { String str = fullNameNumberWithoutSpace.get(i); if (reg.equals(str)) { scoreAndHits.nameIndex = i; scoreAndHits.score = Match_Level_Complete; scoreAndHits.pairs.add(new PointPair(i, -1)); scoreAndHits.matchLevel = Level_Complete; return scoreAndHits; } } for (int i = 0; i < phones.size(); i++) { PhoneStruct phone = phones.get(i); if (reg.equals(phone.phoneNumber)) { scoreAndHits.nameIndex = i; scoreAndHits.score = Match_Level_Complete; scoreAndHits.pairs.add(new PointPair(i, -1)); scoreAndHits.matchType = Match_Type_Phone; scoreAndHits.matchLevel = Level_Complete; return scoreAndHits; } } // 走到這裏說明沒有匹配 return new ScoreAndHits(-1, 0f, new ArrayList<PointPair>()); }
private ScoreAndHits foreAcronymOverFlowMatch(String reg) { // 由於有多是多音字,因此這個方法用來對比不一樣拼音的匹配度,並取最大的那個 ScoreAndHits scoreAndHits = new ScoreAndHits(-1, 0f, new ArrayList<PointPair>()); for (int i = 0; i < fullNameNumber.size(); i++) { ArrayList<String> names = fullNameNumber.get(i); ScoreAndHits tmpscore = foreAcronymOverFlowMatch(names, reg); if (tmpscore.score > scoreAndHits.score) { scoreAndHits = tmpscore; scoreAndHits.nameIndex = i; } } scoreAndHits.matchLevel = Level_Fore_Acronym_Overflow; return scoreAndHits; } // 在第一個字母肯定的狀況下,第二個字母有可能有三種狀況 // 1、在第一個字母所在單詞的鄰居位置charAt(x+1); // 2、在第二個單詞的首字母處 // 3、以上兩種狀況皆不符合,不匹配,出局 private ScoreAndHits foreAcronymOverFlowMatch(ArrayList<String> names, String reg) { // 用來得出某一個拼音的匹配值。 ScoreAndHits scoreAndHits = new ScoreAndHits(-1, 0f, new ArrayList<PointPair>()); if (names.get(0).charAt(0) == reg.charAt(0)) { //其實crossWords()方法纔是求匹配值的方法,lol OverflowMatchValue value = crossWords(names, reg, 0, 0, 0); int cross = crossWords(names, reg, 0, 0, 0).crossed; if (cross > 0) { scoreAndHits.score = Match_Level_Fore_Acronym_Overflow + cross * Match_Score_Reward - (names.size() - cross) * Match_Miss_Punish; scoreAndHits.pairs = value.pairs; } } return scoreAndHits; } /** * 返回一串字符能跨越另外一串字符的長度,根據上面的匹配規則,要儘量的多匹配單詞。若要保證 * 能匹配最長的長度,只要保證下一個字符開始的一段字符能匹配最長的長度便可,換名話說, * 若是想要讓96758匹配最長的字符串,那麼只要保證6758能匹配最長的字符串便可, * 而後758,再而後58……。例如,名字叫PanAnNing,輸入pan,那麼應該匹配三個首字母, * PAN,而不是第一姓的拼音Pan.這是一個遞歸。 * * * @param names * @param regString * 匹配字符串 * @param listIndex * 匹配到的list的第listIndex個單詞 * @param strIndex * 匹配到第listIndex個單詞中的第strIndex個字母 * @param regIndex * regchar的匹配位置,好比匹配到了96758的7上,也就是regIndex==2. * @return */ private OverflowMatchValue crossWords(ArrayList<String> names, String regString, int listIndex, int strIndex, int regIndex) { //在進入此方法時,第listIndex個單詞的第strIndex的字母確定是 // 與regString的第regIndex個字母相等的 OverflowMatchValue result = new OverflowMatchValue(0, false); OverflowMatchValue reser = new OverflowMatchValue(0, false);//返回若是匹配到本單詞的下一個字母能獲得的匹配值 OverflowMatchValue impul = new OverflowMatchValue(0, false);//返回若是匹配到下一個單詞的第一個字母的匹配值 // 仍然以【名字叫PanAnNing,輸入pan(其實對比的是數字,這裏轉化成字母爲了方便)】舉例 // 假設這時listIndex,strIndex,regIndex都是0,因此如今匹配的是p字母,它毫無疑問對應姓名的第一個P, // 那麼下一步應該怎麼作呢,由上面所說【保證下一個字符開始的一段字符能匹配最長的長度便可】 // 也就是說,咱們輸入的pan中的第二個字母a匹配哪一個位置將獲得最優結果。這個盒子中顯然有兩種狀況。 // 一是匹配姓氏Pan中的a,另外一個是匹配名字AnNing中的A。 // reser就表示若是a匹配到Pan中的a最終的匹配值。 // impul就表示若是a匹配到AnNing中的A獲得的最終的匹配值。 if (regIndex < regString.length() - 1) { //若是還沒匹配到最後一個字母,也就是regString還沒匹配到最後一個,那麼將檢測如 //果將regString的下一個字母放到哪裏將獲得最優結果 char nextChar = regString.charAt(regIndex + 1); if (listIndex < names.size() - 1 && nextChar == names.get(listIndex + 1).charAt(0)) { impul = crossWords(names, regString, listIndex + 1, 0, regIndex + 1); } if (strIndex < names.get(listIndex).length() - 1 && nextChar == names.get(listIndex).charAt(strIndex + 1)) { reser = crossWords(names, regString, listIndex, strIndex + 1, regIndex + 1); } //若是上面兩個條件都不成立,那麼就表示本次匹配失敗 } else { result = new OverflowMatchValue((strIndex == 0) ? 1 : 0, true); result.pairs.add(0, new PointPair(listIndex, strIndex)); } if (reser.matched || impul.matched) { //若是其中任意一個方式能夠匹配,那麼結果最大的那個就是最優結果 if (impul.crossed > reser.crossed) { result = impul; } else { result = reser; } result.matched = true; result.crossed = ((strIndex == 0) ? 1 : 0) + Math.max(result.crossed, result.crossed); result.pairs.add(0, new PointPair(listIndex, strIndex)); } return result; } static class OverflowMatchValue { public int crossed = 0; public boolean matched = false; public ArrayList<PointPair> pairs = new ArrayList<PointPair>(); public OverflowMatchValue(int c, boolean m) { this.crossed = c; this.matched = m; } }
跟前置首字母溢出匹配基本同樣,只不過匹配的第一個字母再也不是姓的首字母。ui
private ScoreAndHits backAcronymOverFlowMatch(String reg) { //跟上面差很少 ScoreAndHits scoreAndHits = new ScoreAndHits(-1, 0f, new ArrayList<PointPair>()); for (int i = 0; i < fullNameNumber.size(); i++) { ArrayList<String> names = fullNameNumber.get(i); ScoreAndHits tmp = backAcronymOverFlowMatch(names, reg); if (tmp.score > scoreAndHits.score) { scoreAndHits = tmp; scoreAndHits.nameIndex = i; } } scoreAndHits.matchLevel = Level_Back_Acronym_Overflow; return scoreAndHits; } private ScoreAndHits backAcronymOverFlowMatch(ArrayList<String> names, String reg) { int score = 0; int punish = 0; ScoreAndHits scoreAndHits = new ScoreAndHits(-1, 0f, new ArrayList<PointPair>()); // 有可能會調用屢次crossWords,取決於名字的長度。這是跟前面的不一樣 for (int i = 0; i < names.size(); i++) { String string = (String) names.get(i); if (string.charAt(0) == reg.charAt(0)) { OverflowMatchValue value = crossWords(names, reg, i, 0, 0); int cross = value.crossed; int lost = names.size() - cross; if (cross > score || cross == score && punish > lost) { scoreAndHits.pairs = value.pairs; score = cross; punish = lost; } } } if (score > 0) { scoreAndHits.score = Match_Level_Back_Acronym_Overflow + score * Match_Score_Reward - punish * Match_Miss_Punish; return scoreAndHits; } else { return new ScoreAndHits(-1, 0f, new ArrayList<PointPair>()); } }
4.後置無頭匹配。(難聽就難聽了,反正就那個意思)
this
private ScoreAndHits backHeadlessParagraphMatch(String reg) { // TODO,若是此人有兩個類似的號碼,那麼就只能匹配出一個來了,這是很顯然不對的 int punish = 0; ScoreAndHits scoreAndHits = new ScoreAndHits(-1, -1f, new ArrayList<PointPair>()); scoreAndHits.matchLevel = Level_Headless; scoreAndHits.matchType = Match_Type_Phone; // 不匹配姓名 for (int i = 0; i < phones.size(); i++) { PhoneStruct phone = phones.get(i); int sco = phone.phoneNumber.indexOf(reg); if (sco >= 0) { int lost = phone.phoneNumber.length() - reg.length(); if (scoreAndHits.score < sco || sco == scoreAndHits.score && punish > lost) { scoreAndHits.score = sco; scoreAndHits.nameIndex = i; punish = lost; } //pairs.add放到判斷外面是由於有可能匹配到同一我的的多個手機號碼。 scoreAndHits.pairs.add(new PointPair(i, sco)); } } if (scoreAndHits.score >= 0) { scoreAndHits.score = Match_Level_Headless - scoreAndHits.score * Match_Score_Reward - punish * Match_Miss_Punish; } return scoreAndHits; } //表示電話號碼的一個靜態類,將過濾掉開頭的+86以及系統可能自動生成的「-」以及其餘非數字的字符以便於搜索 public static class PhoneStruct { public String phoneNumber; public int phoneType; public String displayType; public PhoneStruct(String number, int type) { phoneNumber = number.replaceAll("^\\+86", "").replaceAll("[\\]+", ""); phoneType = type; } }