用MFC(C++)實現拼音搜索


2015年4月1日更新:

我在github開源了Objective-C版的拼音搜索項目,感興趣的可以去看看:

OC版拼音搜索


 

 

最近項目需要實現按照拼音搜索資源。在網上找了一下,這方面的東西太少了。

Java有一個開源的實現,但是沒耐心看下去,畢竟對Java不是特別熟練。
C++方面大多都是按照字符的編碼去獲取拼音。這又涉及到GB2312和UTF-8的轉換問題。轉來轉去的,我都暈了。
所以就還是自己寫一個好了。沒用什么比較牛的算法,所以效率上還有很多進步的空間。
支持多音字。字庫文件可以隨時補充。


先把本程序用的的map宏定義一下:

#include <map>
typedef std::multimap<CString, CString>             CStrCStrMultimap;
typedef std::multimap<CString, CString>::iterator   CStrCStrIt;
typedef std::multimap<int, CString>                 IntCStrMultimap;
typedef std::multimap<int, CString>::iterator       IntCStrIt;
typedef std::pair<CStrCStrIt,CStrCStrIt>            CStrIterPair;
typedef std::pair<IntCStrIt,IntCStrIt>              IntIterPair;

首先是實現漢字轉換成拼音。
我的想法是,找一個漢字拼音的對照文件,一個漢字,空格,拼音。多音字的話,每個拼音寫一行就行了。
這樣,就可以很方便地把這個文件一條條地讀出來,塞到一個multimap里面。以漢字為鍵,拼音為值。多音字就是相同的鍵,不同的值,multimap嘛,可以鍵相同的。

BOOL CPinYinHelper::Initialize()
{
    m_mapPinYin.clear();

    CStdioFile sfile;
    char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) );
    setlocale( LC_CTYPE, "chs" );//設定<ctpye.h>中字符處理方式

    if(!sfile.Open(_T("mapfile.txt"), CStdioFile::modeRead))
    {
        m_bInitialized = FALSE;
        return FALSE;
    }

    CString strLine;
    while(sfile.ReadString(strLine))
    {
        strLine.Trim();

        if(!strLine.IsEmpty())
        {
            CString strKey, strVlue;
            TCHAR split = ' ';
            int nBlank = strLine.Find(split);

            if(nBlank > 0)
            {
                strKey = strLine.Left(nBlank);
                strVlue = strLine.Right( strLine.GetLength() - nBlank - 1 );

                nBlank = strVlue.Find(split);
                while(nBlank > 0)
                {
                    m_mapPinYin.insert(std::make_pair(strKey, strVlue.Left(nBlank)));
                    strVlue = strVlue.Right( strVlue.GetLength() - nBlank - 1 );
                    nBlank = strVlue.Find(split);
                }
            }
            m_mapPinYin.insert(std::make_pair(strKey, strVlue));
        }
    }

    sfile.Close();

    setlocale( LC_CTYPE, old_locale );
    free( old_locale );//還原區域設定

    m_bInitialized = TRUE;
    return TRUE;
}

這樣,m_mapPinYin里面就包含了所有漢字及對應的拼音。以后就可以通過漢字直接“查”到它的拼音了。

有了這樣一個map,接下來實現漢字轉拼音就容易了。我在這里,把一句話里轉換出來的拼音仍然是放在一個multimap里。以漢字在字符串中的位置為鍵,拼音為值。多音字就多個值。
如果字庫里沒有找到該字符,說明不是漢字(或者說字庫里還沒有,可以隨時添加的),把它本身作為它的拼音存在multimap里。

BOOL CPinYinHelper::StringToPinYin(CString strIn, IntCStrMultimap &mapOut)
{
    for(int i = 0; i < strIn.GetLength(); ++i)
    {
        CString cChar = CString(strIn.GetAt(i));
        
        if(m_mapPinYin.count(cChar) > 0)
        {
            CStrIterPair cCharValue = m_mapPinYin.equal_range(cChar);

            for (CStrCStrIt it = cCharValue.first; it != cCharValue.second; ++it)
            {
                mapOut.insert(std::make_pair(i, it->second));
            }
        }
        else
        {
            mapOut.insert(std::make_pair(i, cChar));
        }
    }
    
    return TRUE;
}

字符串轉換成拼音了,接下來就該匹配了。匹配的時候有以下幾個規則:
1.支持從字符串任意位置開始匹配
2.支持拼音首字母連續匹配
3.支持拼音全拼連續匹配
4.支持前面任意字符全拼加后面首字母連續匹配
5.支持最后一個漢字部分匹配
就按照這幾個規則用代碼實現就行了。
匹配的時候,用了一個遞歸。因為關鍵字不會太長,所以這里遞歸不會太深,也不是特別影響效率。

BOOL CPinYinHelper::IsMatch(CString strKey, CString strName, BOOL bKeyIncludeChinese, BOOL bIgnorCase)
{
    if(bIgnorCase)
    {
        strKey = strKey.MakeLower();
        strName = strName.MakeLower();
    }

    if(strKey.IsEmpty())
    {
        return TRUE;
    }

    if(strName.IsEmpty())
    {
        return FALSE;
    }

    // 如果當前字符串匹配關鍵字
    if(strName.Find(strKey) > 0)
    {
        return TRUE;
    }

    // 如果待匹配的字符串不包含漢字,直接匹配
    if(!IsIncludeChinese(strName))
    {
        return strName.Find(strKey) > 0;
    }

    // 如果輸入的關鍵字包含漢字,直接匹配
    if(bKeyIncludeChinese)
    {
        return strName.Find(strKey) > 0;
    }

    IntCStrMultimap mapPYName;
    StringToPinYin(strName, mapPYName);

    // 開始匹配。如果之前一位匹配的是全拼(單個字符或數字也算是全拼)
    // 則當前位可以匹配全拼,也可以匹配首字母;
    // 如果之前一位匹配的是首字母,則當前位必須匹配首字母
    int i = 0; // 當前匹配map的第幾個索引
    int nKeySize = strKey.GetLength();

    // 找到第一個首字母匹配的位置,再開始匹配
    CString strFirst = strKey.Left(1);
    while(TRUE)
    {
        if(mapPYName.count(i) == 0)
        {
            mapPYName.clear();
            return FALSE;
        }

        IntIterPair itPair = mapPYName.equal_range(i);
        for(IntCStrIt it = itPair.first; it != itPair.second; ++it)
        {
            CString strPinYin = it->second;

            if(strPinYin.Left(1) == strFirst)
            {
                if(nKeySize <= strPinYin.GetLength())
                {
                    if(strPinYin.Left(nKeySize) == strKey)
                    {
                        return TRUE;
                    }
                }
                if(MatchNext(strKey, mapPYName, i, FALSE))
                {
                    mapPYName.clear();
                    return TRUE;
                }
            }
        }

        ++i;
    }
}


BOOL CPinYinHelper::MatchNext(CString strKey, IntCStrMultimap &mapPYName, int nIndex, BOOL bIsMatchFirst)
{
    BOOL bIsMatch = FALSE;

    // 依次匹配,直到返回TRUE
    int nKeySize = strKey.GetLength();
    if(nKeySize == 0)
    {
        return TRUE;
    }

    IntIterPair itPair = mapPYName.equal_range(nIndex);
    for(IntCStrIt it = itPair.first; it != itPair.second; ++it)
    {
        CString strPinYin = it->second;
        int nPinYinSize = strPinYin.GetLength();
        
        // 先按全拼匹配
        if(!bIsMatchFirst && nPinYinSize < nKeySize)
        {
            if(strKey.Left(nPinYinSize) == strPinYin)
            {
                bIsMatch = MatchNext(strKey.Right(nKeySize - nPinYinSize), mapPYName, ++nIndex, FALSE);
            }
        }

        if(bIsMatch == TRUE)
        {
            return TRUE;
        }

        // 如果當前全拼能夠匹配完剩下的所有關鍵字,則返回TRUE
        // 這里不需要判斷單字符匹配
        if(nPinYinSize >= nKeySize)
        {
            if(strPinYin.Left(nKeySize) == strKey)
            {
                return TRUE;
            }
        }

        // 匹配首字母
        if(strKey.Left(1) == strPinYin.Left(1))
        {
            bIsMatch = MatchNext(strKey.Right(nKeySize - 1), mapPYName, ++nIndex, TRUE);
        }

        if(bIsMatch == TRUE)
        {
            return TRUE;
        }
    }

    return FALSE;
}

這樣,漢字轉拼音,拼音搜索都實現了。很簡單吧!
本來想寫成一個靜態類,那就可以隨用隨調了,而且也不會使字庫的map在內存中存在多份。可惜靜態成員變量必須初始化!!這個multimap真心不知道怎么初始化!
所以只好寫了個單例類。防止每個拼音類的變量都重新創建一個字庫map,增加內存開銷。

#pragma once

#include <map>
typedef std::multimap<CString, CString>             CStrCStrMultimap;
typedef std::multimap<CString, CString>::iterator   CStrCStrIt;
typedef std::multimap<int, CString>                 IntCStrMultimap;
typedef std::multimap<int, CString>::iterator       IntCStrIt;
typedef std::pair<CStrCStrIt,CStrCStrIt>            CStrIterPair;
typedef std::pair<IntCStrIt,IntCStrIt>              IntIterPair;

// CConvertToPinYin command target

class CPinYinHelper
{
public:
    static CPinYinHelper *GetInstance()
    {
        if(NULL == m_pInstance)
        {
            m_pInstance = new CPinYinHelper;
        }

        return m_pInstance;
    }

    // 初始化操作
    BOOL Initialize();

    // 判斷是否初始化
    BOOL IsInitialized();

    // 釋放資源
    BOOL ReleaseMap();

    // 判斷字符串是否包含漢字
    BOOL IsIncludeChinese(CString strIn);

    // 將包含漢字的字符串轉換成拼音
    BOOL StringToPinYin(IN CString strIn, OUT IntCStrMultimap &mapOut);

    // 判斷輸入的關鍵字是否與當前字符串(字符串的拼音)匹配
    BOOL IsMatch(CString strKey, CString strName, BOOL bKeyIncludeChinese, BOOL bIgnorCase = TRUE);


private:
    // 從漢字拼音的第nIndex個索引開始匹配strKey
    BOOL MatchNext(CString strKey, IntCStrMultimap &mapPYName, int nIndex, BOOL bIsMatchFirst);


private:
    //實現單例模式,構造函數私有
    //防止其他方式產生實例方式,賦值構造函數/拷貝構造函數私有
    CPinYinHelper();
    ~CPinYinHelper();
    CPinYinHelper(const CPinYinHelper&);
    CPinYinHelper& operator= (const CPinYinHelper&);

    //私有內嵌類
    //它的唯一工作就是在析構函數中刪除CPinYinHelper的實例
    class CGarbo
    {
    public:
        ~CGarbo()
        {
            if(CPinYinHelper::m_pInstance)
            {
                delete CPinYinHelper::m_pInstance;
                CPinYinHelper::m_pInstance = NULL;
            }
        }
    };

private:
    static CPinYinHelper *m_pInstance;
    static CGarbo m_Garbo;

    CStrCStrMultimap m_mapPinYin;
    BOOL m_bInitialized;
};

 

如果有高手看到這篇文章,麻煩你給指點一二,謝謝了!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM