拼音分词拆解算法(用于判断字符串是否是纯拼音构成,并分离出所有拼音,50行纯原生代码实现)
废话哔哔
都说算法是程序的灵魂,算法源于数学,数学是描述宇宙万物的语言,这话一点不假,开发出身算法用的较少,回过头看算法,用到了,递归、循环、分支、分治、合并、取舍调优的思想,确实精彩,烧脑还挺有意思。
好的技术博客必须有做到有No BB,Show Code的干货,也得有详细的关键点用于辅助,因此写了这篇博客。
需求
- 概括:将一个字符字符串,拆分单个的拼音,例如bianchengyuyan(编程语言),拆分成bian、cheng、yu、yan。如果是zhang1san(张1三),zhangssan(张s三),则返回不是拼音。
- 应用场景:这算法并非百无一用,拼音分词,词义分析,品相判别等场景能够用到。
成品代码
<?php
/**
* @function 返回拼音组合,由于冗长,单独拆分出来,拼音手动总结,可能存在遗漏
* @return array
*/
function pinYinArr() {
return [
1 => ['a', 'e', 'm', 'n', 'o'],
2 => ['ai','an','ao','ba','bi','bo','bu','ca','ce','ci','cu','da','de','di','du','en','er','fa','fo','fu','ga','ge','gu','ha','he','hm','hu','ji','ju','ka','ke','ku','la','le','li','lo','lu','lv','ma','me','mi','mo','mu','na','ne','ng','ni','nu','nv','ou','pa','pi','po','pu','qi','qu','re','ri','ru','sa','se','si','su','ta','te','ti','tu','wa','wo','wu','xi','xu','ya','ye','yi','yo','yu','za','ze','zi','zu'],
3 => ['ang','bai','ban','bao','bei','ben','bie','bin','cai','can','cao','cen','cha','che','chi','chu','cou','cui','cun','cuo','dai','dan','dao','dei','den','dia','die','diu','dou','dui','dun','duo','eng','fan','fei','fen','fou','gai','gan','gao','gei','gen','gou','gua','gui','gun','guo','hai','han','hao','hei','hen','hng','hou','hua','hui','hun','huo','jia','jie','jin','jiu','jue','jun','kai','kan','kao','kei','ken','kou','kua','kui','kun','kuo','lai','lan','lao','lei','lia','lie','lin','liu','lou','lue','lun','luo','mai','man','mao','mei','men','mie','min','miu','mou','nai','nan','nao','nei','nen','nie','nin','niu','nou','nue','nuo','pai','pan','pao','pei','pen','pie','pin','pou','qia','qie','qin','qiu','que','qun','ran','rao','ren','rou','rua','rui','run','ruo','sai','san','sao','sen','sha','she','shi','shu','sou','sui','sun','suo','tai','tan','tao','tie','tou','tui','tun','tuo','wai','wan','wei','wen','xia','xie','xin','xiu','xue','xun','yan','yao','yin','you','yue','yun','zai','zan','zao','zei','zen','zha','zhe','zhi','zhu','zou','zui','zun','zuo'],
4 => ['bang','beng','bian','biao','bing','cang','ceng','chai','chan','chao','chen','chou','chua','chui','chun','chuo','cong','cuan','dang','deng','dian','diao','ding','dong','duan','fang','feng','gang','geng','gong','guai','guan','hang','heng','hong','huai','huan','jian','jiao','jing','juan','kang','keng','kong','kuai','kuan','lang','leng','lian','liao','ling','long','luan','mang','meng','mian','miao','ming','nang','neng','nian','niao','ning','nong','nuan','pang','peng','pian','piao','ping','qian','qiao','qing','quan','rang','reng','rong','ruan','sang','seng','shai','shan','shao','shei','shen','shou','shua','shui','shun','shuo','song','suan','tang','teng','tian','tiao','ting','tong','tuan','wang','weng','xian','xiao','xing','xuan','yang','ying','yong','yuan','zang','zeng','zhai','zhan','zhao','zhei','zhen','zhou','zhua','zhui','zhun','zhuo','zong','zuan'],
5 => ['chang','cheng','chong','chuai','chuan','guang','huang','jiang','jiong','kuang','liang','niang','qiang','qiong','shang','sheng','shuai','shuan','xiang','xiong','zhang','zheng','zhong','zhuai','zhuan'],
6 => ['chuang', 'shuang', 'zhuang'],
];
}
/**
* @function 移除参数1中右边包含的参数2,并返回剩余的字符,例如strRemoveRightOnce('wahaha', 'ha'),返回waha
* @param $string string 被操作字符串
* @param $remove string 要被移除的字符串
* @return string
*/
function strRemoveRightOnce($str, $part) {
if (substr($str, -strlen($part)) == $part) {
return substr($str, 0, - strlen($part));
}
return $str;
}
/**
* @function 获取字符串存在的拼音数量,不兼容-符号,从长往短了截取
* @param $str string 字符
* @param $result int 函数返回的结果
* @return array
*/
function pinYinStrLongToShort($str, $result = []) {
if($str == '') {
return $result;
}
if(($str == '') && ($result == [])) {
return [];
}
//判断是否是纯拼音,不是直接过滤
if((! preg_match('/^[a-z]+$/', $str)) && ($result == [])) {
return [];
}
$initial_arr = pinYinArr();
$initial_keys = array_keys($initial_arr);
$max = max($initial_keys);
$min = min($initial_keys);
for($i = $max; $i >= $min; $i--) {
$substring = substr($str, - $i);
if(in_array($substring, $initial_arr[$i])) {
//避免xiao ha ha,用ltrim函数,一次性移除掉了两个ha造成的计量有误
array_unshift($result, $substring);
return pinYinStrLongToShort(strRemoveRightOnce($str, $substring), $result);
} else {
if($i == $min) {
return [];
}
}
}
return $result;
}
print_r(pinYinStrLongToShort('bianchengyuyan'));
Array
(
[0] => bian
[1] => cheng
[2] => yu
[3] => yan
)
算法调优注意的地方
- 如果检测到含有非拼音的字符(例如有数字),以及多余的拼音字符(例如zhangxsan张x三),会直接返回空数组。
- 代码采用从长往短切割的策略,以xianggang(香港)举例:
- 从长到短(for循环递减):分成xiang、gang。粒度大,但是失败率小。
- 从短到长(for循环递增):拆分成xi、an,后面的gg没法切了。粒度更小,容易出错。
- 从长到短缺点也很明显:相应的会忽略精度,所以xian(西安)会记作一个拼音,当做xian(贤)处理。
- 代码采用的从字符串右边往左切的策略,进一步避免切割出错。例如xianguang(闲逛):
- 从左到右从长到短:xiang、uang没办法切了。
- 从左到右从短到长:xi、an、gu、an、g没办法切了。
- 从右到左从长到短:guang、xian,刚刚好。
- 从右到左从短到长:ang、gu、an、xi,也行。
- 结语:从长往短了切割用于减少失败率,从右往左切割,用于进一步避免出错,因此被采用。
- 注意:以上算法,并非适合所有场景,可能存在误差,毕竟不是NLP。
- 补充:若读者想要获取最少或者最多的拼音,可复制3份代码,并修改for循环的策略,列出4种情况做对比,方可选出最适合当前场景的算法。
热门相关:腹黑老公圈宠:逃妻抱回家 纨绔毒医 隐婚99天:首长,请矜持 我的抖音太无敌 完全育种:约束美学 重生嫡女谋天下