• 用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;
    };

    如果有高手看到这篇文章,麻烦你给指点一二,谢谢了!

  • 相关阅读:
    vue中dom元素和组件的获取
    Vue.js中父子组件之间的传值和传方法
    IDEA中的快捷键
    springmvc中使用controller时,跳转视图会带上外层的地址
    通配符的匹配很全面, 但无法找到元素 'mvc:annotation-driven' 的声明
    vue中的组件
    vuejs
    成员变量(实例变量)&局部变量&静态变量(类变量)的区别
    代码块
    重载&重写
  • 原文地址:https://www.cnblogs.com/tangzhengyue/p/3306010.html
Copyright © 2020-2023  润新知