智能撥號匹配算法(一)

    

    完整源碼在個人github上 https://github.com/NashLegend/QuicKid    java


    

    智能撥號是指,呃不用解釋了,國內撥號軟件都帶的你們都知道,就是輸入姓名拼音的一部分就可快速搜索出聯繫人的撥號方式。以下圖git

wKioL1RQ4j_Bu7A3AAE0u2jIJJM541.jpg


    智能匹配,很容易想到的就是先把九宮格輸入鍵盤上輸入的數字轉換成可能的拼音組合,而後再用這些可能的拼音與聯繫人列表中的姓名拼音一一匹配,取匹配度最高的排到最前,可是這有一個問題就是數組對應的可能的拼音組合實在是點兒多,跑一下下面的代碼就知道了。若是想智能一些的話還要先剔除一些不可能的拼音組合實在有點麻煩。
github

public static HashMap<Character, String[]> keyMaps;

public static void main(String[] args) {
	keyMaps = new HashMap<Character, String[]>();
	keyMaps.put('0', new String[0]);
	keyMaps.put('1', new String[0]);
	keyMaps.put('2', new String[] { "a", "b", "c" });
	keyMaps.put('3', new String[] { "d", "e", "f" });
	keyMaps.put('4', new String[] { "g", "h", "i" });
	keyMaps.put('5', new String[] { "j", "k", "l" });
	keyMaps.put('6', new String[] { "m", "n", "o" });
	keyMaps.put('7', new String[] { "p", "q", "r", "s" });
	keyMaps.put('8', new String[] { "t", "u", "v" });
	keyMaps.put('9', new String[] { "w", "x", "y", "z" });

	List<String> lss = getPossibleKeys("726");
	System.out.println(lss.size());
}

public static ArrayList<String> getPossibleKeys(String key) {
	ArrayList<String> list = new ArrayList<String>();
	if (key.length() > 0) {
		if (key.contains("1") || key.contains("0")) {
			list.add(key);
		} else {
			int keyLen = key.length();
			String[] words;
			if (keyLen == 1) {
				words = keyMaps.get(key.charAt(0));
				for (int i = 0; i < words.length; i++) {
					list.add(words[i]);
				}
			} else {
				ArrayList<String> sonList = getPossibleKeys(key.substring(
						0, key.length() - 1));
				words = keyMaps.get(key.charAt(key.length() - 1));
				for (int i = 0; i < words.length; i++) {
					for (Iterator<String> iterator = sonList.iterator(); iterator
							.hasNext();) {
						String sonStr = iterator.next();
						list.add(sonStr + words[i]);
					}
				}
			}
		}
	}
	return list;
}


    因此能夠反過來想,爲何必定要匹配拼音呢。其實咱們能夠匹配數字,將姓名的拼音轉化成九宮格上的數字,好比張三就是94264,726。用輸入的數字來匹配這些數字,匹配次數將大大減小。匹配出的數值越高,匹配度越強。數組


下面先定義一下幾個匹配規則

  1. 徹底匹配用來匹配姓名和電話號碼。指輸入字符串與聯繫人內某一匹配項徹底匹配。無加減分項。less

    PanZhiHui-->PanZhiHuiide

  2. 前置首字母徹底匹配用來匹配姓名。指輸入字符串與聯繫人前幾個首字母徹底匹配。用來匹配姓名。是前置首字母溢出匹配的特殊形式。 無加分項,減分項爲不匹配的首字母個數。ui

    PZH-->PanZhiHui。+2-0
    PZ-->PanZhiHui。+2-1this

  3. 前置首字母溢出匹配用來匹配姓名。指在匹配首字母的狀況下,還匹配了某一個或者幾個首字母后一段連貫的字符串。加分項爲匹配到的首字母個數,減分項爲不匹配的首字母個數。spa

    PanZH-->PanZhiHui。+1-0
    PZhiHui-->PanZhiHui。+1-0
    PZHui-->PanZhiHui。+1-0
    PZHu-->PanZhiHui。+1-0
    PZhi-->PanZhiHui。+1-1code

  4. 前置段匹配用來匹配姓名。指一個長度爲N的連貫字符與聯繫人內某一匹配項的前N個字符徹底匹配。是前置首字母溢出匹配的特殊形式。

    panzh-->PanZhiHui

  5. 後置首字母徹底匹配用來匹配姓名。指輸入字符串匹配除第一個首字母之外的其餘幾個連續首字母。 無加分項,減分項爲不匹配的首字母個數。

    ZH-->PanZhiHui

  6. 後置首字母溢出匹配用來匹配姓名。後置首字母徹底匹配的狀況下,還匹配了某一個或者幾個首字母后一段連貫的字符串。加分項爲匹配的首字母的數量,減分項爲不匹配的首字母個數。

    ZHu-->PanZhiHui。+1-0
    Zh-->PanZhiRui。+1-1

  7. 後置段匹配用來匹配姓名。指有一串長度爲N的連貫字符與與聯繫人內某一匹配項的後半部的一段N個字符串匹配,且此連貫字符的開頭位置必須是某一首字母位置。是後置首字母溢出匹配的特殊形式,同時意味着後置首字母溢出匹配事實上不須要加分項,只要保證後置首字母徹底匹配的加分項比它大就足夠了

    ZhiHui/Zhi/Hui-->PanZhiHui

  8. 後置無頭匹配用來匹配姓名和電話號碼。指一串連貫字符在前7種所有未匹配成功的狀況下,卻被包含在字符串裏。加分項爲-index,減分項爲長度差

    hiHui-->PanZhiHui

每一個規則都有一個基礎數值,以及加分減分項,基本數值不一樣。取減分項爲0.001,加分項爲1。至於爲何,在下一段。

查詢時匹配以上8種,其餘狀況不匹配。

匹配的原則是匹配儘量多的單詞。

上面這些名字徹底是臨時胡編亂造的好麼 0.0j_0004.gif

排列規則

  1. 查詢出的列表將按匹配度排序,匹配度是一個float(固然double也同樣),優先級別從高到低以下(減分項足夠小以致於高優先級的匹配度不管如何減分都仍然會高於下面的優先級,所以減分項事實上只用來區別同一優先級中不一樣聯繫人匹配程度的高低)。

  • 徹底匹配,對應的基礎數值爲4000。

  • 前置首字母徹底匹配、前置首字母溢出匹配、前置段匹配,這三個其實均可以視做前置首字母溢出匹配,對應的基礎數值爲3000。(當只有一個字母時,按規則#1算)

  • 後置首字母徹底匹配、後置首字母溢出匹配、後置段匹配,這三個其實均可以視做後置首字母溢出匹配對應的基礎數值爲2000。(當只有一個字母時,按規則#5算)

  • 後置無頭匹配。對應的基礎數值爲1000。(能夠考慮摒棄此匹配,沒有人會這麼按,而按鍵出錯的可能性致使無頭匹配的可能性又極小,每每不是想要的結果)



輸入的一列查詢字符串將同時與聯繫人的名字和電話匹配。對於一個聯繫人,他的名字可能有多種發音,這時候要取匹配度最高的。對於一個聯繫人,他可能有兩個甚至更多的電話號碼,匹配的時候要分別匹配,而不是單獨取匹配度最高的。




    


    好了,先寫一個類Contact。

    添加幾個常量,看字面意思應該看得懂。

static final int Match_Type_Name = 1;
static final int Match_Type_Phone = 2;

static final int Level_Complete = 4;
static final int Level_Fore_Acronym_Overflow = 3;
static final int Level_Back_Acronym_Overflow = 2;
static final int Level_Headless = 1;
static final int Level_None = 0;

static final float Match_Level_None = 0;
static final float Match_Level_Headless = 1000;
static final float Match_Level_Back_Acronym_Overflow = 2000;
static final float Match_Level_Fore_Acronym_Overflow = 3000;
static final float Match_Level_Complete = 4000;
static final float Match_Score_Reward = 1;
static final float Match_Miss_Punish = 0.001f;
static final int Max_Reward_Times = 999;
static final int Max_Punish_Times = 999;

    再添加下面幾條字段

    List<ArrayList<String>> fullNameNumber = new ArrayList<ArrayList<String>>();
    List<String> fullNameNumberWithoutSpace = new ArrayList<String>();
    List<String> abbreviationNumber = new ArrayList<String>();

    fullNameNumber是一個二維的ArrayList,它存放的是將一個聯繫人打散後數字後的List。好比張三的fullNameString就是{{94264,726}},之因此是二維的,緣由是有可能姓名是含有多音字……

    fullNameNumberWithoutSpace是聯繫人姓名的全拼對應的數字,好比張三就是{94264726},之因此是二維的,緣由是有可能姓名是含有多音字……

    abbreviationNumber是聯繫人姓名首字母對應的數字,好比張三對應的就是{97},之因此是二維的,緣由是有可能姓名是含有多音字……

    在設置了Contact的名字後上面三個字段將同時生成數據。

    synchronized public void initPinyin() {
            String trimmed = name.replaceAll(" ", "");
            //將姓名轉化爲拼音
            String fullNamesString = HanyuPinyinHelper.hanyuPinYinConvert(trimmed, false);
            for (Iterator<String> iterator = fullNamesString.iterator(); iterator
                    .hasNext();) {
                String str = iterator.next();
                ArrayList<String> lss = new ArrayList<String>();
                String[] pinyins = TextUtil.splitIgnoringEmpty(str, " ");
                String abbra = "";
                String fullNameNumberWithoutSpaceString = "";
                for (int i = 0; i < pinyins.length; i++) {
                    String string = pinyins[i];
                    String res = convertString2Number(string);
                    abbra += res.charAt(0);
                    fullNameNumberWithoutSpaceString += res;
                    lss.add(res);
                }
                abbreviationNumber.add(abbra);
                fullNameNumberWithoutSpace
                        .add(fullNameNumberWithoutSpaceString);
                fullNameNumber.add(lss);
            }
    }

給它一個match方法。下面調用的xxxMatch()方法都是針對四種不一樣種類的匹配的對應方法。

public float match(String reg) {
	// 沒法經過第一個字母來判斷是否是後置匹配
	// 可是能夠經過第一個字母判斷是否是前置匹配
	// match的原則是匹配儘量多的字符
	// 事實上前五種匹配方式均可以使用crossMatch來實現
	ScoreAndHits scoreAndHits = new ScoreAndHits(-1, 0f,
			new ArrayList<PointPair>());
	if (!TextUtils.isEmpty(reg)) {
		boolean checkBack = !canPrematch(reg);
		if (!checkBack) {
			if ((scoreAndHits = completeMatch(reg)).score == 0f) {
				if ((scoreAndHits = foreAcronymOverFlowMatch(reg)).score == 0f) {
					checkBack = true;
				}
			}
		}
		if (checkBack) {
			if ((scoreAndHits = backAcronymOverFlowMatch(reg)).score == 0f) {
				scoreAndHits = backHeadlessParagraphMatch(reg);
			}
		}
	}
	scoreAndHits.reg = reg;
	matchValue = scoreAndHits;
	return scoreAndHits.score;
}

全部的xxxMatch返回的結果是一個自定義類ScoreAndHits。

public static class ScoreAndHits {
	public float score = 0f;
	public int nameIndex;
	public ArrayList<PointPair> pairs = new ArrayList<PointPair>();
	public int matchType = Match_Type_Name;
	public int matchLevel = Level_None;
	public String reg = "";

	public ScoreAndHits(int nameIndex, float score,
			ArrayList<PointPair> pairs) {
		this.nameIndex = nameIndex;
		this.score = score;
		this.pairs = pairs;
	}
}

nameIndex是匹配到了第幾個拼音。score是匹配度。pairs是指匹配到的數字在對應的二維list中的位置,用來未來高亮顯示匹配的字符用的。若是徹底匹配的話,就用不到pairs了。


幾個匹配方法的具體內容看下一篇,超過字數限制,寫不開了j_0012.gif

相關文章
相關標籤/搜索