首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 獲取漢字拼音首字母:兩種高效演算法實戰解析

     編輯:PHP 相關知識

需求背景

在中文系統開發中,常需將漢字轉換為拼音首字母,應用場景包含:通訊錄排序、搜尋索引、URL 友善化、資料分組等。本文深度解析兩種主流實作方案:GB2312 區間對照表法與二分法查找優化版,並提供完整可用程式碼。

方案一:GB2312 編碼區間對照法(簡潔高效)

核心原理

  • 漢字在 GB2312 編碼中,按拼音順序排列
  • 每個漢字佔 2 個位元組,計算公式:ASC = (高位元組 * 256 + 低位元組) - 65536
  • 依據 ASC 值落入的區間,對應 A-Z 首字母

完整實作程式碼

<?php
/**
 * 取得單一漢字的拼音首字母(大寫)
 * @param string $s0 單一漢字(UTF-8 編碼)
 * @return string|null 首字母或 null
 */
function getFirstChar($s0) {
    $fchar = ord($s0{0});
    // 英數字直接返回大寫
    if ($fchar >= ord("A") && $fchar <= ord("z")) return strtoupper($s0{0});

    // UTF-8 轉 GB2312
    $s1 = iconv("UTF-8", "gb2312", $s0);
    $s2 = iconv("gb2312", "UTF-8", $s1);
    $s = ($s2 == $s0) ? $s1 : $s0; // 確認轉換可逆

    // 計算 GB2312 區位碼
    $asc = ord($s{0}) * 256 + ord($s{1}) - 65536;

    // 區間判斷(依 GB2312 拼音排序)
    if ($asc >= -20319 && $asc <= -20284) return "A";
    if ($asc >= -20283 && $asc <= -19776) return "B";
    if ($asc >= -19775 && $asc <= -19219) return "C";
    if ($asc >= -19218 && $asc <= -18711) return "D";
    if ($asc >= -18710 && $asc <= -18527) return "E";
    if ($asc >= -18526 && $asc <= -18240) return "F";
    if ($asc >= -18239 && $asc <= -17923) return "G";
    if ($asc >= -17922 && $asc <= -17418) return "H"; // 註:標準無 I
    if ($asc >= -17417 && $asc <= -16475) return "J";
    if ($asc >= -16474 && $asc <= -16213) return "K";
    if ($asc >= -16212 && $asc <= -15641) return "L";
    if ($asc >= -15640 && $asc <= -15166) return "M";
    if ($asc >= -15165 && $asc <= -14923) return "N";
    if ($asc >= -14922 && $asc <= -14915) return "O";
    if ($asc >= -14914 && $asc <= -14631) return "P";
    if ($asc >= -14630 && $asc <= -14150) return "Q";
    if ($asc >= -14149 && $asc <= -14091) return "R";
    if ($asc >= -14090 && $asc <= -13319) return "S";
    if ($asc >= -13318 && $asc <= -12839) return "T";
    if ($asc >= -12838 && $asc <= -12557) return "W";
    if ($asc >= -12556 && $asc <= -11848) return "X";
    if ($asc >= -11847 && $asc <= -11056) return "Y";
    if ($asc >= -11055 && $asc <= -10247) return "Z";

    return null;
}

/**
 * 字串轉拼音首字母(支援中英混合)
 * @param string $zh UTF-8 字串
 * @return string 首字母串
 */
function pinyinFirst($zh) {
    $ret = "";
    $s1 = iconv("UTF-8", "gb2312", $zh);
    $s2 = iconv("gb2312", "UTF-8", $s1);
    $zh = ($s2 == $zh) ? $s1 : $zh; // 確保為 GB2312

    for ($i = 0; $i < strlen($zh); $i++) {
        $s1 = substr($zh, $i, 1);
        $p = ord($s1);
        if ($p > 160) { // 高位元組 > 160 為漢字區
            $s2 = substr($zh, $i, 2); // 取雙位元組
            $ret .= getFirstChar($s2);
            $i++; // 跳過低位元組
        } else {
            $ret .= $s1; // 英數字保留
        }
    }
    return strtoupper($ret);
}

// 測試
header("Content-Type: text/html; charset=UTF-8");
echo "測試字串:這是中文字符串<br/>";
echo "結果:" . pinyinFirst('這是中文字符串'); // 輸出:ZSZWZFC
?>

優缺點分析

優點缺點
程式碼簡短,無需外部資料表依賴 GB2312 編碼順序,不支援繁體/擴展字
效能極佳,O(n) 線性掃描需 iconv 擴展支援
邏輯直觀,易於維護修改區間表需精確,易漏邊界字

方案二:二分法查找優化版(功能完整、支援擴展)

核心優勢

  • 使用預建立的「漢字編碼→首字母」對照表(約 26 筆關鍵節點)
  • 採用二分法查找,時間複雜度 O(log n),極快
  • 完整處理 UTF-8/GB2312 雙編碼、中英混合、數字符號保留
  • 修復傳統演算法「Z 讀成 Y」等邊界錯誤

完整類別實作

<?php
class ChinesePinyin {
    // 關鍵編碼節點:GB2312 編碼值 => 首字母
    private $_pinyins = array(
        176161 => 'A', 176197 => 'B', 178193 => 'C', 180238 => 'D',
        182234 => 'E', 183162 => 'F', 184193 => 'G', 185254 => 'H',
        187247 => 'J', 191166 => 'K', 192172 => 'L', 194232 => 'M',
        196195 => 'N', 197182 => 'O', 197190 => 'P', 198218 => 'Q',
        200187 => 'R', 200246 => 'S', 203250 => 'T', 205218 => 'W',
        206244 => 'X', 209185 => 'Y', 212209 => 'Z'
    );

    private $_charset = 'utf-8';

    public function __construct($charset = 'utf-8') {
        $this->_charset = $charset;
    }

    // 公開介面:取得字串首字母
    public function getInitials($str) {
        if (empty($str)) return '';

        // 純 ASCII 字串直接返回
        if ($this->_isAscii($str[0]) && $this->_isAsciis($str)) {
            return $str;
        }

        // UTF-8 轉 GB2312 處理
        if ($this->_charset == 'utf-8') {
            $str = iconv('utf-8', 'gb2312', $str);
        }

        $words = $this->_cutWord($str);
        $result = array();

        foreach ($words as $word) {
            if ($this->_isAscii($word)) { // 英數字
                $result[] = $word;
                continue;
            }

            // 計算 GB2312 編碼值
            $code = ord(substr($word, 0, 1)) * 1000 + ord(substr($word, 1, 1));

            // 二分法查找
            if (($idx = $this->_search($code)) !== -1) {
                $result[] = $this->_pinyins[$idx];
            }
        }
        return strtoupper(implode('', $result));
    }

    // 私有方法:字串切割為單字陣列(漢字=2位元組,英數=1位元組)
    private function _cutWord($str) {
        $words = array();
        while ($str != '') {
            if ($this->_isAscii($str)) {
                $words[] = $str[0];
                $str = substr($str, 1);
            } else {
                $words[] = substr($str, 0, 2);
                $str = substr($str, 2);
            }
        }
        return $words;
    }

    private function _isAscii($char) {
        return ord(substr($char, 0, 1)) < 160;
    }

    private function _isAsciis($str) {
        $len = min(3, strlen($str));
        for ($i = 0; $i < $len; $i++) {
            if (!$this->_isAscii($str[$i])) return false;
        }
        return true;
    }

    // 二分法查找核心
    private function _search($code) {
        $data = array_keys($this->_pinyins);
        $lower = 0;
        $upper = count($data) - 1;

        if ($code < $data[0]) return -1;

        while ($lower <= $upper) {
            $mid = (int)(($lower + $upper) / 2);
            if ($data[$mid] < $code) {
                $lower = $mid + 1;
            } elseif ($data[$mid] == $code) {
                return $data[$mid];
            } else {
                $upper = $mid - 1;
            }
        }
        // 找不到精確匹配,返回小於 code 的最大節點
        return ($lower > 0) ? $data[$lower - 1] : -1;
    }
}

// 使用範例
$py = new ChinesePinyin('utf-8');
echo $py->getInitials('啊吧才的餓飛就好i就看了嗎你哦平去人是他uv我想一在');
// 輸出:ABCDEFGHIJKLMNOPQRSTUVWXYZ
?>

兩大方案對比選型

維度區間對照法二分法查找版
程式碼複雜度低(單一函數)中(類別封裝)
擴充性難(需手動維護區間)強(修改對照表即可)
邊界字準確度需仔細測試邊界高(二分法自動定位)
編碼支援GB2312 為主UTF-8/GB2312 雙支援
適用場景簡單專案、效能極致要求正式產品、需長期維護

進階建議與替代方案

  1. Composer 套件:現代專案建議使用 overtrue/pinyin(基於詞庫,支援多音字、分詞)
    composer require overtrue/pinyin
  2. 多音字處理:上述兩方案均為單音字處理,嚴謹場景需引入詞庫(如 CC-CEDICT)
  3. 效能優化:高頻呼叫時,將實例化移出迴圈,或使用靜態快取
  4. 編碼檢測:輸入編碼不確定時,先用 mb_detect_encoding() 判斷再轉碼

結語

獲取漢字首字母是中文系統的基礎功能。區間對照法適合快速原型、簡單需求;二分法查找版結構更清晰、擴充性更強,適合正式專案。隨著專案發展,建議遷移至成熟的 Composer 套件,獲得更完整的多音字、分詞支援,專注於核心業務邏輯開發。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved