博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用MFC(C++)实现拼音搜索
阅读量:6137 次
发布时间:2019-06-21

本文共 7742 字,大约阅读时间需要 25 分钟。

2015年4月1日更新:

我在github开源了Objective-C版的拼音搜索项目,感兴趣的可以去看看:


 

 

最近项目需要实现按照拼音搜索资源。在网上找了一下,这方面的东西太少了。

Java有一个开源的实现,但是没耐心看下去,毕竟对Java不是特别熟练。

C++方面大多都是按照字符的编码去获取拼音。这又涉及到GB2312和UTF-8的转换问题。转来转去的,我都晕了。
所以就还是自己写一个好了。没用什么比较牛的算法,所以效率上还有很多进步的空间。
支持多音字。字库文件可以随时补充。

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

#include typedef std::multimap
CStrCStrMultimap;typedef std::multimap
::iterator CStrCStrIt;typedef std::multimap
IntCStrMultimap;typedef std::multimap
::iterator IntCStrIt;typedef std::pair
CStrIterPair;typedef std::pair
IntIterPair;

首先是实现汉字转换成拼音。

我的想法是,找一个汉字拼音的对照文件,一个汉字,空格,拼音。多音字的话,每个拼音写一行就行了。
这样,就可以很方便地把这个文件一条条地读出来,塞到一个multimap里面。以汉字为键,拼音为值。多音字就是相同的键,不同的值,multimap嘛,可以键相同的。

BOOL CPinYinHelper::Initialize(){    m_mapPinYin.clear();    CStdioFile sfile;    char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) );    setlocale( LC_CTYPE, "chs" );//设定
中字符处理方式 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 typedef std::multimap
CStrCStrMultimap;typedef std::multimap
::iterator CStrCStrIt;typedef std::multimap
IntCStrMultimap;typedef std::multimap
::iterator IntCStrIt;typedef std::pair
CStrIterPair;typedef std::pair
IntIterPair;// CConvertToPinYin command targetclass 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;};

 

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

转载于:https://www.cnblogs.com/tangzhengyue/p/3306010.html

你可能感兴趣的文章
word2010中去掉红色波浪线的方法
查看>>
fabric上下文管理器(context mangers)
查看>>
JQuery-EasyUI Datagrid数据行鼠标悬停/离开事件(onMouseOver/onMouseOut)
查看>>
并发和并行的区别
查看>>
php小知识
查看>>
Windows下安装、运行Lua
查看>>
Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解(二)
查看>>
初识中间件之消息队列
查看>>
MyBatis学习总结(三)——优化MyBatis配置文件中的配置
查看>>
Spring常用注解
查看>>
我的友情链接
查看>>
PCS子层有什么用?
查看>>
查看端口,关闭端口
查看>>
代码托管平台简介
查看>>
linux:yum和apt-get的区别
查看>>
Sentinel 1.5.0 正式发布,引入 Reactive 支持
查看>>
数据库之MySQL
查看>>
2019/1/15 批量删除数据库相关数据
查看>>
数据类型的一些方法
查看>>
Mindjet MindManager 2019使用教程:
查看>>