本程序是把输入的字符串转化为以空格间隔的拼音串,
如输入“zhongguoren",则会输出“zhong guo ren".
另外程序也利用了Pinyin4j.jar的包,处理开始时先把中文汉字转化为拼音(但对多音字支持不好,如:银行-->yin xing),先不管这个问题。
说白本程序就是把输入的英文字符串,按照拼音规则分割,不过其中也遇到一些问题,现在记录下来。其实网上我也找过,不过就是没实现出来。
第一种做法:把拼音字典从a ai an ang一直往下读入内存(其实共407个拼音而已),对字符串,从长度为1开始至字符串末尾,不断截取,在拼音字典内二分查找,
若存在,则连接下个字符,继续在拼音字典内二分查找;
若不存在,则证明这是拼音的最大匹配了,就作为结果保存;
直到字符串结束。
(测试多次后发现有bug,bug 1: deng,xiong这类拼音不能识别,加了判断ng结尾(已修正);
bug 2: 对tian这个拼音识别成: ti an两个拼音。
本人觉得如果再加判断就会显得很难看。所以放弃这种做法。
其中pinyinDict的数据结构是String[]. DictOper.readDict()方法是读文件,并把每一行转化String,最终返回String数组。
拼音字典文件格式如下:
a
ai
an
ang
ao
ba
bai
...
代码如下:
/** * 通过拼音字典,二分查找是否存在拼音<br/> * 贪婪算法,最大匹配拼音序列, bug: tiantian会分成ti an ti an * * @param inputChar * @return * @author chow 2010-8-25 上午10:58:22 */ @Deprecated public String _processString(char[] inputChar) { String temp = new String(inputChar); String[] strArray = temp.split(" "); StringBuffer result = new StringBuffer(); if (pinyinDict == null || pinyinDict.length <= 0) { pinyinDict = DictOper.readPYDict(); } for (int i = 0; i < strArray.length; i++) { String curStr = strArray[i]; int curStrLen = curStr.length(); boolean existWord = false; for (int beginIndex = 0, endIndex = 1; endIndex <= curStrLen; endIndex++) { String tmpKey = curStr.substring(beginIndex, endIndex); int index = Arrays.binarySearch(pinyinDict, tmpKey); // int gap = endIndex - beginIndex; if (index >= 0) { // 存在,则继续找下个字符 if (endIndex == curStrLen) { result.append(tmpKey + " "); } else if (endIndex + 2 <= curStrLen && curStr.substring(endIndex, endIndex + 2).equals( "ng")) { // 若后面两个字母是ng,则接上去 result.append(curStr .substring(beginIndex, endIndex + 2) + " "); beginIndex = endIndex + 2; endIndex = beginIndex; existWord = false; continue; } existWord = true; continue; } // 不存在且前面一个字符是存在的 else if (existWord) { result.append(curStr.substring(beginIndex, --endIndex)); result.append(" "); beginIndex = endIndex; existWord = false; continue; } } } // System.out.println("result:" + result.toString()); return result.toString(); }
第二种做法:
改变拼音字典的数据结构,采用Map<String, List>的格式,如下:
a:[a, ai, an, ang, ao]
b:[ba, bai, ban, bang, bao, bei, ben, beng, bi, bian, biao, bie, bin, bing, bo, bu]
...
程序的思想是:从字符串的第一个字符开始,若字符在字典里存在,则取出其对应的拼音串,并从该字符往后截取1个至5个字符,
(如:输入tianxia,第一个字符为't',则取出t:[ta, tai, tan, tang, tao, te, tei, teng, ti, tian, tiao, tie, ting, tong, tou, tu, tuan, tui, tun, tuo],
并截取t, ti, tia, tian, tianx, tianxi 六组字符串,取出最长一个匹配串[tian],将结果保存,从'tian'的'n'后一个字符开始循环,直至到字符串结束。
/** * 根据Map查找是否存在对应的拼音<br/> * 贪婪算法,最大匹配拼音序列 * * @param inputChar * @return 以空格间隔的拼音字符串,eg: zhong guo ren * @author chow 2010-8-25 上午11:00:07 */ public String _processStringByMap(char[] inputChar) { String temp = new String(inputChar); temp = PinyinHelper.toHanyuPinyinString(temp, outputFormat, ""); String[] strArray = temp.split(" "); StringBuffer result = new StringBuffer(); if (pyData == null) { pyData = DictOper.getPYData(); } for (int i = 0; i < strArray.length; i++) { String curStr = strArray[i]; int curStrLen = curStr.length(); int beginIndex = 0, nextWordIndex = 0; while (beginIndex < curStrLen) { String firstLetter = curStr.substring(beginIndex, beginIndex + 1); List<String> list = pyData.get(firstLetter); if (list == null) { beginIndex += 1; nextWordIndex = beginIndex; continue; } for (int subLen = 1; subLen <= 6; subLen++) { if (beginIndex + subLen > curStrLen) { break; } String piece = curStr.substring(beginIndex, beginIndex + subLen); if (list.contains(piece)) { nextWordIndex = subLen + beginIndex; } } // 若不存在任何匹配,begin和next都向后移一位 if (nextWordIndex == beginIndex) { beginIndex += 1; nextWordIndex = beginIndex; continue; } String subStr = curStr.substring(beginIndex, nextWordIndex); result.append(subStr + " "); beginIndex = nextWordIndex; } } if (result.length() == 0) { result.append(temp); } return result.toString(); }
做法二可以很好识别拼音串,但回头想想,其实程序还可以优化。
因为每个拼音字母可以组成的拼音的长度范围是可预见的。就是说以't'开头的拼音最短为长度为2(eg: ta),最长为4(eg: tian);
这时只要改变拼音字典的数据结构就可以了,写个程序统计一下各个拼音最长和最短的长度,更改后的拼音字典为:
a:[a, ai, an, ang, ao]
min:1,max:3
b:[ba, bai, ban, bang, bao, bei, ben, beng, bi, bian, biao, bie, bin, bing, bo, bu]
min:2,max:4
对于min和max可以用Map<String,Integer[]> pyLenMap保存,
for (int subLen = 1; subLen <= 6; subLen++) { if (beginIndex + subLen > curStrLen) { break; }
对于上面的for循环内的1与6可换成pyLenMap的min和max。
这样程序循环的次数就能更少。
另外附上拼音字典。