針對 GB2312 編碼漢字,利用區位碼區間映射到拼音,以二分查找實現 O(log N) 轉換。本文提供原始碼完整整理、邊界處理修正、C++17 現代化重構、UTF-8 支援擴展方案。
GB2312 一級漢字 3755 字,按拼音排序。每個漢字佔 2 字節:高位字節 = 區碼 + 0xA0、低位字節 = 位碼 + 0xA0。將區位碼合併為 unsigned short,建立有序陣列 code_pin[] 對應 str_pin[]。
#include <iostream>
#include <string>
#include <cstdint>
#include <cstring>
// 區位碼邊界表 (376 個分界點)
static const uint16_t code_pin[] = {
0xb0a1,0xb0a3,0xb0b0,0xb0b9,0xb0bc,0xb0c5,0xb0d7,0xb0df,0xb0ee,0xb0fa,
0xb1ad,0xb1bc,0xb1c0,0xb1c6,0xb1de,0xb1ea,0xb1ee,0xb1f2,0xb1f8,0xb2a3,
// ... (共 376 項,原文完整陣列)
0xd7f0,0xd7f2
};
// 對應拼音表 (不含聲調)
static const char* str_pin[] = {
"a","ai","an","ang","ao","ba","bai","ban","bang","bao",
"bei","ben","beng","bi","bian","biao","bie","bin","bing","bo",
// ... (共 376 項)
"zuo"
};
static const size_t PIN_SIZE = sizeof(code_pin)/sizeof(code_pin[0]);
// 二分查找單漢字拼音
const char* get_pinyin(uint16_t gb_code) {
size_t lo = 0, hi = PIN_SIZE - 1;
while (hi - lo > 1) {
size_t mid = (lo + hi) / 2;
if (code_pin[mid] == gb_code) return str_pin[mid];
if (code_pin[mid] < gb_code) lo = mid;
else hi = mid;
}
// 區間 [lo, hi],取不超過 gb_code 的最大邊界
return str_pin[(code_pin[hi] <= gb_code) ? hi : lo];
}
// GB2312 字串轉拼音 (無分隔)
std::string to_pinyin(const std::string& gb_str) {
std::string result;
for (size_t i = 0; i < gb_str.size(); ) {
uint8_t hi_byte = gb_str[i];
if (hi_byte < 0x80) { // ASCII
result.push_back(hi_byte);
i++;
} else { // 雙字節漢字
if (i + 1 >= gb_str.size()) break;
uint8_t lo_byte = gb_str[++i];
uint16_t gb_code = (hi_byte << 8) | lo_byte;
result += get_pinyin(gb_code);
i++;
}
}
return result;
}
// 帶空格分隔版
std::string to_pinyin_spaced(const std::string& gb_str) {
std::string result;
bool first = true;
for (size_t i = 0; i < gb_str.size(); ) {
uint8_t hi_byte = gb_str[i];
if (hi_byte < 0x80) {
if (!first && (i>0 && (uint8_t)gb_str[i-1] >= 0x80)) result.push_back(' ');
result.push_back(hi_byte);
i++;
} else {
if (!first) result.push_back(' ');
first = false;
if (i + 1 >= gb_str.size()) break;
uint8_t lo_byte = gb_str[++i];
uint16_t gb_code = (hi_byte << 8) | lo_byte;
result += get_pinyin(gb_code);
i++;
}
}
return result;
}
int main() {
// 測試:需 GB2312 編碼源檔或輸入
std::string test = "中國人民"; // 假設源檔為 GB2312
std::cout << to_pinyin_spaced(test) << std::endl;
// 預期輸出: zhong guo ren min
return 0;
}
| 問題 | 原文 | 修正 |
|---|---|---|
| 邊界查找邏輯 | 高位優先返回 | 雙邊界 lo/hi,取不超過目標的最大邊界 |
| 簽名型別 | unsigned short | uint16_t 明確 16 位 |
| 字串處理 | 修改輸入指標 input[++i] | 只讀索引 i,不修改原串 |
| ASCII 判斷 | high < 0x80 | 明確 uint8_t 避免負值擴展 |
| 編碼假設 | 隱含 GB2312 | 文件註明、運行時檢測或轉碼 |
GB2312 已被 GBK/GB18030/UTF-8 取代。現代方案:
pypinyin (Python)、cpp-pinyin (C++)、go-pinyin (Go)
// 需連結 libiconv
#include <iconv.h>
std::string utf8_to_gbk(const std::string& utf8) {
iconv_t cd = iconv_open("GBK", "UTF-8");
// ... 標準 iconv 轉換流程 ...
iconv_close(cd);
return gbk_str;
}
// 使用
std::string utf8_input = u8"中華人民共和國";
std::string gbk = utf8_to_gbk(utf8_input);
std::string pinyin = to_pinyin_spaced(gbk);
code_pin/str_pin 編成 constexpr 或嵌入式資源,編譯期常量傳播gperf 生成 O(1) 查找,無分支預測失敗str_pin 含數字聲調(如 "zhong1"),或輸出結構體GB2312 區位碼二分查找是中文轉拼音的經典輕量級方案,適合嵌入式、無標準庫環境、舊系統維護。現代專案建議直接使用成熟開源庫(支援 UTF-8、多音字、分詞),僅在極度受限環境下手寫映射表。理解「編碼→碼點→查表→拼音」的流程,對處理中文 NLP 前置任務大有裨益。