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 > )
{
strKey = strLine.Left(nBlank);
strVlue = strLine.Right( strLine.GetLength() - nBlank - ); nBlank = strVlue.Find(split);
while(nBlank > )
{
m_mapPinYin.insert(std::make_pair(strKey, strVlue.Left(nBlank)));
strVlue = strVlue.Right( strVlue.GetLength() - nBlank - );
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 = ; i < strIn.GetLength(); ++i)
{
CString cChar = CString(strIn.GetAt(i)); if(m_mapPinYin.count(cChar) > )
{
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) > )
{
return TRUE;
} // 如果待匹配的字符串不包含汉字,直接匹配
if(!IsIncludeChinese(strName))
{
return strName.Find(strKey) > ;
} // 如果输入的关键字包含汉字,直接匹配
if(bKeyIncludeChinese)
{
return strName.Find(strKey) > ;
} IntCStrMultimap mapPYName;
StringToPinYin(strName, mapPYName); // 开始匹配。如果之前一位匹配的是全拼(单个字符或数字也算是全拼)
// 则当前位可以匹配全拼,也可以匹配首字母;
// 如果之前一位匹配的是首字母,则当前位必须匹配首字母
int i = ; // 当前匹配map的第几个索引
int nKeySize = strKey.GetLength(); // 找到第一个首字母匹配的位置,再开始匹配
CString strFirst = strKey.Left();
while(TRUE)
{
if(mapPYName.count(i) == )
{
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() == 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 == )
{
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() == strPinYin.Left())
{
bIsMatch = MatchNext(strKey.Right(nKeySize - ), 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;
};

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

用MFC(C++)实现拼音搜索的更多相关文章

  1. 【Solr】 solr对拼音搜索和拼音首字母搜索的支持

    问:对于拼音和拼音首字母的支持,当你在搜商品的时候,如果想输入拼音和拼音首字母就给出商品的信息,怎么办呢? 实现方式有2种,但是他们其实是对应的.  用lucene实现 1.建索引, 多建一个索引字段 ...

  2. 从零搭建 ES 搜索服务(四)拼音搜索

    一.前言 上篇介绍了 ES 的同义词搜索,使我们的搜索更强大了,然而这还远远不够,在实际使用中还可能希望搜索「fanqie」能将包含「番茄」的结果也罗列出来,这就涉及到拼音搜索了,本篇将介绍如何具体实 ...

  3. php根据汉字获取拼音(php基于拼音搜索实现原理)

    php根据汉字获取拼音(php基于拼音搜索实现原理) 代码一:获取字符串汉字首字母,兼容GBK和UTF-8 <?php function getfirstchar($s0){   //获取单个汉 ...

  4. elasticsearch实战 中文+拼音搜索

    需求 雪花啤酒  需要搜索雪花.啤酒 .雪花啤酒.xh.pj.xh啤酒.雪花pj ik导入 参考https://www.cnblogs.com/LQBlog/p/10443862.html,不需要修改 ...

  5. Simple: 一个支持中文和拼音搜索的 sqlite fts5插件

    之前的工作关系,需要在手机上支持中文和拼音搜索.由于手机上存储数据一般都是用 sqlite,所以是基于 sqlite3 fts5 来实现.这段时间再次入门 c++,所以想用 c++ 实现一下,一来用于 ...

  6. elasticsearch之拼音搜索

    拼音搜索在中文搜索环境中是经常使用的一种功能,用户只需要输入关键词的拼音全拼或者拼音首字母,搜索引擎就可以搜索出相关结果.在国内,中文输入法基本上都是基于汉语拼音的,这种在符合用户输入习惯的条件下缩短 ...

  7. android开发之使用拼音搜索汉字

    国庆回了趟家,昨天真不想走,离家近的感觉太好.唉,不扯这些,说说今天的正事吧. 上篇博客中介绍了自定义AutoCompleteTextView ,但是用到了一个很蹩脚的技术,就是我们事先把每个汉字的拼 ...

  8. jquery实现仿select列表的即时搜索及拼音搜索

    这里提到select,其实不是select,而是用<li><input>标签去仿造一个select,以实现对已有“option”的快速检索功能. 以<input>标 ...

  9. iOS拼音搜索,拼音首字母搜索

    扩展了一下 搜索框,能够实现拼音和首字母模糊搜索 基本搜索 上一篇文章 #import "NSString+utility.h" @interface WJWPinyinSearc ...

随机推荐

  1. Python能做什么?

    Python作为一个功能强大,并且简单易学的编程语言而广受好评,那么Python都能做些什么呢?概括起来有以下几个方面: 1.Web开发: 2.大数据处理: 3.人工智能: 4.自动化运维: 5.云计 ...

  2. 基于spring框架的apache shiro简单集成

    关于项目的安全保护,我一直想找一个简单配置就能达到目的的方法,自从接触了shiro,这个目标总算达成了,以下结合我使用shiro的经验,谈谈比较轻便地集成该功能. 首先我们先了解一下shiro是什么. ...

  3. PAT甲题题解-1107. Social Clusters (30)-PAT甲级真题(并查集)

    题意:有n个人,每个人有k个爱好,如果两个人有某个爱好相同,他们就处于同一个集合.问总共有多少个集合,以及每个集合有多少人,并按从大到小输出. 很明显,采用并查集.vis[k]标记爱好k第一次出现的人 ...

  4. Photoshop一些常用的快捷键

    1.按住Alt键,点击图层上的小眼睛,只显示当前图层 2.新建纯色图层,抠图 3.Fn+ num 调整不透明度 4.Shift + num 调整流量 5.调整图层透明度,在移动工具状态下,输入数字:0 ...

  5. Linux命令(十二) 分割文件 split 合并文件 join

    一.分割文件 split 命令介绍 当处理文件时,有时需要将文件做分割处理,split 命令用于分割文件,可以分割文本文件,按指定的行数分割,每个分割后的文件都包含相同的行数.split 可以分割非文 ...

  6. 贝云cms内容管理系统(thinkphp5.0开源cms管理系统)

    byCms包含文章,图片,下载,视频模型,基于thinkphp5.0.9,可无缝升级至thinkphp.1.0,是一套简单,易用的内容管理系统,旨在帮助开发者节约web应用后台开发时间和精力,以最快的 ...

  7. Python进阶-配置文件

    一. 什么是配置文件?为什么要做配置文件? 将所有的代码和配置都变成模块化可配置化,这样就提高了代码的重用性,不再每次都去修改代码内部,这个就是我们逐步要做的事情,可配置化 二. 配置文件长啥样? 配 ...

  8. BZOJ 3516 国王奇遇记加强版(乱推)

    题意 求\(\sum_{k=1}^{n}k^mm^k (n\leq1e9,m\leq1e3)\) 思路 在<>中有一个方法用来求和,称为摄动法. 我们考虑用摄动法来求这个和式,看能不能得到 ...

  9. 二分图最大权匹配模板(pascal)

    用uoj80的题面了: 从前一个和谐的班级,有 nlnl 个是男生,有 nrnr 个是女生.编号分别为 1,…,nl1,…,nl 和 1,…,nr1,…,nr. 有若干个这样的条件:第 vv 个男生和 ...

  10. 【题解】 [SDOI2009] Elaxia的路线(最短路+拓扑排序)

    懒得复制,戳我戳我 Solution: 题目大概意思就是找两条最短路后,找出最长公共部分 我们就只用以四个点为源点开始走\(SPFA\),然后我们就只用遍历每条边然后建立一个新的拓扑图,然后随便搞一下 ...