拼音分词拆解算法(用于判断字符串是否是纯拼音构成,并分离出所有拼音,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    $part   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 array  函数返回的结果
 * @return   array
 */
function pinYinCutLongToShort($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])) {
            array_unshift($result, $substring);
            //避免xiao ha ha,用ltrim函数,一次性移除掉了两个ha造成的计量有误
            return pinYinCutLongToShort(strRemoveRightOnce($str, $substring), $result);
        } else {
            if($i == $min) {
                return [];
            }
        }
    }

    return $result;
}


print_r(pinYinCutLongToShort('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的AI算法加持(自然语言处理)。
  • 补充:若读者想要获取最细粒度的拼音,不必再原有函数上改动,可以将返回的数组结果遍历,再次调用另一个切割函数(注意另一个函数是从短到长切割),随后汇总。
posted @ 2024-08-07 00:47  小松聊PHP进阶  阅读(103)  评论(0编辑  收藏  举报