拼音分詞拆解演算法(用於判斷字串是否是純拼音構成,並分離出所有拼音,50行純原生程式碼實現)

小松聊PHP进阶發表於2024-08-07

廢話嗶嗶

都說演算法是程式的靈魂,演算法源於數學,數學是描述宇宙萬物的語言,這話一點不假,開發出身演算法用的較少,回過頭看演算法,用到了,遞迴、迴圈、分支、分治、合併、取捨調優的思想,確實精彩,燒腦還挺有意思。
好的技術部落格必須有做到有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演算法加持(自然語言處理)。
  • 補充:若讀者想要獲取最細粒度的拼音,不必再原有函式上改動,可以將返回的陣列結果遍歷,再次呼叫另一個切割函式(注意另一個函式是從短到長切割),隨後彙總。

相關文章