[WPF] 離線環境實現支援拼音模糊搜尋的AutoCompleteBox

czwy發表於2024-07-24

AutoCompleteBox是一個常見的提高輸入效率的元件,很多WPF的第三方控制元件庫都提供了這個元件,但基本都是字串的子串匹配,不支援拼音模糊匹配,例如無法透過輸入ldhliudehua匹配到劉德華。要實現拼音模糊搜尋功能,通常會採用分詞、資料庫等技術對待匹配資料集進行預處理。某些場景受制於條件限制,無法對資料進行預處理,本文將介紹在這種情況下如何實現支援拼音模糊搜尋的AutoCompleteBox,先來看下實現效果。
image

主要思路

WPF中並沒有AutoCompleteBox控制元件,我們可以使用TextBox輸入搜尋內容,用Popup+ListBox顯示匹配到的提示內容。拼音模糊匹配漢字則採用字串匹配的方式來解決,也就是搜尋字串和待匹配資料集的內容全部轉換為拼音字串,然後進行子串匹配。這裡有三個問題需要解決。

  1. 漢字轉換為拼音。
  2. 拼音如何匹配。 例如ldhlidhldhualiudehuadhuahua等都能匹配到劉德華
  3. 匹配後的內容高亮顯示。 當輸入dhua匹配到劉德華時需要把德華兩個字高亮。

漢字轉換拼音

微軟為了開發者實現國際化語言的互轉,提供了Microsoft Visual Studio International Pack,這個擴充套件包裡面有中文、日文、韓文、英語等各國語言包,並提供方法實現互轉、獲取拼音、獲取字數、甚至獲取筆畫數等等。下載Microsoft Visual Studio International Pack 1.0 SR1安裝後,在安裝目錄中找到ChnCharInfo.dll,然後在專案中新增引用。
ChnCharInfo.dll獲取漢字的拼音時只能傳入單個字元,因此只能把漢字字串拆分成一個個字元處理,由於漢字存在多音字情況以及缺少語義資訊,獲取的拼音組合可能是多個,例如輸入長江,返回的是changjiangzhangjiang。漢字轉拼音的方法如下:

/// <summary>
/// 獲取漢字拼音
/// </summary>
/// <param name="str">待處理包含漢字的字串</param>
/// <param name="split">拼音分隔符</param>
/// <returns></returns>
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;
}

拼音匹配演算法

漢字轉換後的拼音字串有多組,只要搜尋字串轉換的拼音組合有一組與待匹配字串轉換的拼音組合中匹配,則認為匹配成功,為了後續高亮顯示,需要記錄下匹配的起始位置以及匹配的子串長度。程式碼如下:

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方法是拼音匹配演算法的核心,是在【演算法】拼音匹配演算法這篇博文中演算法的基礎上稍作修改,詳細的思路及圖解可閱讀這篇博文。

高亮匹配的子串

WPF中可以透過TextEffectPositionStartPositionCount以及Foreground屬性設定字串中需要高亮內容的起始位置、長度以及高亮顏色。前面拼音匹配演算法中獲取了匹配成功子串的起始位置和長度,也正是為此做準備。之前在WPF使用TextBlock實現查詢結果高亮顯示一文中有詳細介紹思路和程式碼,此處不再贅述。

小結

本文介紹了在不依賴資料庫及分詞的情況下如何實現拼音模糊搜尋並在目標字串中高亮顯示,方法中也存在諸多不足需要完善的地方。

  1. 匹配策略存在誤匹配。例如輸入,可以匹配出拼音為shi的所有漢字。
  2. 匹配演算法效率不夠高。測試過程中,待匹配資料集中模擬了500條資料,匹配耗時大概在400~500ms左右。

程式碼示例

ChinesePhoneticizeFuzzyMatch

相關文章