先介绍下背景,最近有个需求,需要将用户的好友列表按照昵称的拼音首字母排序,类似于手机电话簿的联系人。一开始建议让终端同学去做这点,毕竟终端现有的电话簿模块已经实现了,不过终端同学说电话簿是系统组件,移植出来需要额外开发,导入库等等。建议后台来实现,终端直接接受处理好的数据,于是就由后台来做了。对于这个问题刚开始没有什么思路,不知道怎么着手,问题包括如何识别昵称里的中文、中文如何转换为拼音等等。咨询了组内的大佬,给出建议:首先,中文字符是可以识别出来的,因为我们编码都是统一的utf8编码,utf8是unicode编码的一种实现方式,在unicode编码标准中,中文字符的unicode编码范围是0X4E00~0X9FA5,可以通过检测这一范围来识别中文,这样,第一个问题解决了。其次,中文转拼音,预先准备好一个字典map,存储中文和拼音的对应关系,转换的时候直接读取map。这里中文拼音对应关系其实已有前辈做过,github上有了映射文件,可以直接拿来使用。
两个难题解决了,方案就出来了,分为三部分:首先是拼音map的生成处理,其次是中文的识别以及转换,最后是业务处理。demo的效果图如下:
图1. github上的中文拼音字典格式,链接放在参考文档里
图2. 编译运行截图
参考代码:
1. 字典解析部分
/* PinyinMapParser.h */
#include <map>
#include <string>
using namespace std;
class PinyinMapParser
{
public:
PinyinMapParser(){}
~PinyinMapParser(){}
public:
// 从文件中获取拼音字典
static int GetPinYinMap(string& path, map<string, string>& pin_yin_map);
};
/* PinyinMapParser.cpp */
#include <iostream>
#include <fstream>
#include "PinyinMapParser.h"
// 获取拼音映射文件
int PinyinMapParser::GetPinYinMap(string& path, map<string, string>& pin_yin_map)
{
// 路径校验
if (path.empty())
{
cout<<"path emtpy, invalid param"<<endl;
return -1;
}
// 读取拼音文件
std::ifstream is(path.c_str());
if (!is.is_open())
{
cout<<"open file:"<<path<<" error"<<endl;
return -1;
}
while (!is.eof())
{
string tmp_pinyin;
// 每次读取一行,这里拼音文件格式:王=wang1,wang2,数字表示声调
getline(is, tmp_pinyin);
//cout<<"getline:"<<tmp_pinyin<<endl;
if (tmp_pinyin.find("=") != string::npos)
{
string zh, pinyin;
size_t i = tmp_pinyin.find_first_of('=');
if (i != string::npos && i != tmp_pinyin.size()-1)
{
// 发音有多个,我们只取一个
zh.assign(tmp_pinyin, 0, i);
pinyin.assign(tmp_pinyin, i+1, tmp_pinyin.size()-i-1);
// 去掉拼音末尾声调
if (pinyin.find(",") != string::npos)
{
size_t j = pinyin.find(",");
pinyin.assign(pinyin, 0, j-1);
}
else
{
pinyin.assign(pinyin.begin(), pinyin.end()-1);
}
// 取出来后放到字典里
//cout<<"zh:"<<zh<<", pinyin:"<<pinyin<<endl;
pin_yin_map[zh] = pinyin;
}
}
}
// 关闭文件
is.close();
return 0;
}
2. 中文转换部分:
/* Convert.h */
#include <map>
#include <string>
using namespace std;
class Convert
{
public:
Convert(){}
~Convert(){}
public:
// 昵称转换中文
static int ConvertNicknameToPinyin(string& nickname, string& convert_str, map<string, string>& pinyin_map);
// 判断是否是中文
static bool IsZh(const char* p);
};
/* Convert.cpp */
#include <iostream>
#include <string>
#include "Convert.h"
// 获取拼音映射文件
int Convert::ConvertNicknameToPinyin(string& nickname, string& convert_str, map<string, string>& pinyin_map)
{
// 检查昵称是否为空
if (nickname.empty())
{
cout<<"nickname is empty, invalid"<<endl;
return -1;
}
{
size_t i = 0;
string tmp_str;
// 逐个字符检查
while(i < nickname.length())
{
try
{
tmp_str.clear();
const char *p = &nickname.at(i);
// 中文占用三字节,utf8中三字节编码第一个字节前四位是1110
if((*p & 0xF0) == 0xE0 && IsZh(p))
{
tmp_str.append(&nickname.at(i), 3);
// 从字典里找对应的拼音
if (pinyin_map.find(tmp_str) != pinyin_map.end())
{
convert_str.append(pinyin_map[tmp_str]);
}
else
{
cout<<"can't find zh pinyin,zh:"<<tmp_str<<endl;
convert_str.append(tmp_str);
}
i += 3;
}
else
{
convert_str.append(p,1);
i += 1;
}
}
catch (...)
{
cout<<"exception occurs"<<endl;
return -1;
}
}
}
return 0;
}
// 判断是否是中文
bool Convert::IsZh(const char* p)
{
if ( NULL == p)
{
cout<<"input param null"<<endl;
return false;
}
// 中文unicode编码范围是0X4E00~0X9FA5,即utf8范围0xe4b880 ~ 0xe9baa5
// 使用utf8编码占3个字节,下面是分别对三个字节校验,utf8编码介绍 https://blog.csdn.net/zhusongziye/article/details/84261211
if ((*p&0xF0) == 0xE0)
{
if( *(p+1) == '\0' || *(p+2) == '\0')
{
return false;
}
unsigned char v = *p;
unsigned char v1 = *(p+1);
unsigned char v2 = *(p+2);
if( (v1&0xC0) != 0x80 || (v2&0xC0) != 0x80 )
{
return false;
}
if( v < 0xE4)
{
return false;
}
if( v == 0xE4 && v1 < 0xB8)
{
return false;
}
if( v == 0xE4 && v1 == 0xB8 && v2 < 0x80)
{
return false;
}
if(v > 0xE9)
{
return false;
}
if(v == 0xE9 && v1 > 0xBE)
{
return false;
}
if(v == 0xE9 && v1 == 0xBE && v2 > 0xB5)
{
return false;
}
p += 3;
return true;
}
return false;
}
3. 主函数
#include <iostream>
#include <map>
#include <string>
#include "PinyinMapParser.h"
#include "Convert.h"
using namespace std;
int main (int argc, char** argv)
{
int ret = 0;
if (argc < 3)
{
cout<<"Usage: ./exe file_path"<<endl;
return -1;
}
string path(argv[1]);
//cout<<"input param:"<<path<<endl;
map<string, string> m_pinyin;
// 解析拼音字典
ret = PinyinMapParser::GetPinYinMap(path, m_pinyin);
if (ret != 0)
{
cout<<"GetPinYinMap error, ret:"<<ret<<", path:"<<path<<endl;
return ret;
}
string zh(argv[2]);
string zhpinyin;
// 拼音转换
ret = Convert::ConvertNicknameToPinyin(zh, zhpinyin, m_pinyin);
if (ret != 0)
{
cout<<"parse "<<zh<<" error"<<endl;
return ret;
}
cout<<"parse succ, zh:"<<zh<<", pinyin:"<<zhpinyin<<endl;
return ret;
}
参考资料:
3. github 拼音链接
================================================================================================