AutoCompleteBox是一個(gè)常見的提高輸入效率的組件,很多WPF的第三方控件庫(kù)都提供了這個(gè)組件,但基本都是字符串的子串匹配,不支持拼音模糊匹配,例如無法通過輸入ldh
或liudehua
匹配到劉德華
。要實(shí)現(xiàn)拼音模糊搜索功能,通常會(huì)采用分詞、數(shù)據(jù)庫(kù)等技術(shù)對(duì)待匹配數(shù)據(jù)集進(jìn)行預(yù)處理。某些場(chǎng)景受制于條件限制,無法對(duì)數(shù)據(jù)進(jìn)行預(yù)處理,本文將介紹在這種情況下如何實(shí)現(xiàn)支持拼音模糊搜索的AutoCompleteBox,先來看下實(shí)現(xiàn)效果。
主要思路#
WPF中并沒有AutoCompleteBox控件,我們可以使用TextBox
輸入搜索內(nèi)容,用Popup
+ListBox
顯示匹配到的提示內(nèi)容。拼音模糊匹配漢字則采用字符串匹配的方式來解決,也就是搜索字符串和待匹配數(shù)據(jù)集的內(nèi)容全部轉(zhuǎn)換為拼音字符串,然后進(jìn)行子串匹配。這里有三個(gè)問題需要解決。
漢字轉(zhuǎn)換為拼音。
拼音如何匹配。 例如ldh
、lidh
、ldhua
、liudehua
、dhua
、hua
等都能匹配到劉德華
匹配后的內(nèi)容高亮顯示。 當(dāng)輸入dhua
匹配到劉德華
時(shí)需要把德華
兩個(gè)字高亮。
漢字轉(zhuǎn)換拼音#
微軟為了開發(fā)者實(shí)現(xiàn)國(guó)際化語言的互轉(zhuǎn),提供了Microsoft Visual Studio International Pack,這個(gè)擴(kuò)展包里面有中文、日文、韓文、英語等各國(guó)語言包,并提供方法實(shí)現(xiàn)互轉(zhuǎn)、獲取拼音、獲取字?jǐn)?shù)、甚至獲取筆畫數(shù)等等。下載Microsoft Visual Studio International Pack 1.0 SR1安裝后,在安裝目錄中找到ChnCharInfo.dll
,然后在項(xiàng)目中添加引用。
ChnCharInfo.dll
獲取漢字的拼音時(shí)只能傳入單個(gè)字符,因此只能把漢字字符串拆分成一個(gè)個(gè)字符處理,由于漢字存在多音字情況以及缺少語義信息,獲取的拼音組合可能是多個(gè),例如輸入長(zhǎng)江
,返回的是changjiang
和zhangjiang
。漢字轉(zhuǎn)拼音的方法如下:
public static List<string> GetChinesePhoneticize(string str, string split = ""){
List<string> result = new List<string>();
char[] chs = str.ToCharArray();
Dictionary<int, List<string>> totalPhoneticizes = new Dictionary<int, List<string>>();
for (int i = 0; i < chs.Length; i++)
{
var phoneticizes = new List<string>();
if (ChineseChar.IsValidChar(chs[i]))
{
ChineseChar cc = new ChineseChar(chs[i]);
phoneticizes.AddRange(cc.Pinyins.Where(r => !string.IsNullOrWhiteSpace(r)).ToList<string>().ConvertAll(p => Regex.Replace(p, @"\d", "").ToLower()).Distinct());
}
else
{
phoneticizes.Add(chs[i].ToString());
}
if (phoneticizes.Any())
totalPhoneticizes[i] = phoneticizes;
}
foreach (var phoneticizes in totalPhoneticizes)
{
var items = phoneticizes.Value;
if (result.Count <= 0)
{
result = items;
}
else
{
var newtotalPhoneticizes = new List<string>();
foreach (var totalPingYin in result)
{
newtotalPhoneticizes.AddRange(items.Select(item => totalPingYin + split + item));
}
newtotalPhoneticizes = newtotalPhoneticizes.Distinct().ToList();
result = newtotalPhoneticizes;
}
}
return result;
}
拼音匹配算法#
漢字轉(zhuǎn)換后的拼音字符串有多組,只要搜索字符串轉(zhuǎn)換的拼音組合有一組與待匹配字符串轉(zhuǎn)換的拼音組合中匹配,則認(rèn)為匹配成功,為了后續(xù)高亮顯示,需要記錄下匹配的起始位置以及匹配的子串長(zhǎng)度。代碼如下:
public static bool fuzzyMatchChar(string character, string input, out int matchStart, out int matchCount){
List<string> regexs = GetChinesePhoneticize(input);
List<string> targetStr = GetChinesePhoneticize(character, " ");
matchStart = -1;
matchCount = 0;
foreach (string regex in regexs)
{
foreach (string target in targetStr)
{
if (PhoneticizeMatch(regex, target.Split(' '), out matchStart, out matchCount))
return true;
}
}
return false;
}
這里的PhoneticizeMatch
方法是拼音匹配算法的核心,是在【算法】拼音匹配算法這篇博文中算法的基礎(chǔ)上稍作修改,詳細(xì)的思路及圖解可閱讀這篇博文。
高亮匹配的子串#
WPF中可以通過TextEffect
的PositionStart
、PositionCount
以及Foreground
屬性設(shè)置字符串中需要高亮內(nèi)容的起始位置、長(zhǎng)度以及高亮顏色。前面拼音匹配算法中獲取了匹配成功子串的起始位置和長(zhǎng)度,也正是為此做準(zhǔn)備。之前在WPF使用TextBlock實(shí)現(xiàn)查找結(jié)果高亮顯示一文中有詳細(xì)介紹思路和代碼,此處不再贅述。
小結(jié)#
本文介紹了在不依賴數(shù)據(jù)庫(kù)及分詞的情況下如何實(shí)現(xiàn)拼音模糊搜索并在目標(biāo)字符串中高亮顯示,方法中也存在諸多不足需要完善的地方。
匹配策略存在誤匹配。例如輸入石
,可以匹配出拼音為shi
的所有漢字。
匹配算法效率不夠高。測(cè)試過程中,待匹配數(shù)據(jù)集中模擬了500條數(shù)據(jù),匹配耗時(shí)大概在400~500ms左右。
代碼示例#
ChinesePhoneticizeFuzzyMatch