similar_text() 是 PHP 內置的字符串相似度計算函數,返回兩字符串匹配字符數,可選計算百分比。本文深入分析其 C 源碼實現,揭示底層算法邏輯。
函數採用「尋找最長公共子串 → 分治遞歸處理左右剩餘部分」的策略,屬於動態規劃思想的簡化實現。
static void php_similar_str(const char *txt1, int len1, const char *txt2, int len2,
int *pos1, int *pos2, int *max) {
char *p, *q;
char *end1 = (char *) txt1 + len1;
char *end2 = (char *) txt2 + len2;
int l;
*max = 0;
// 雙重循環遍歷所有可能的起始位置組合
for (p = (char *) txt1; p < end1; p++) {
for (q = (char *) txt2; q < end2; q++) {
// 向後匹配,直到字符不同或到達末尾
for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++);
// 更新最長匹配記錄
if (l > *max) {
*max = l;
*pos1 = p - txt1;
*pos2 = q - txt2;
}
}
}
}
時間複雜度:O(n×m×min(n,m)),對長字符串性能較差。
輸出:max 最長公共子串長度,pos1/pos2 其在兩字符串中的起始位置。
static int php_similar_char(const char *txt1, int len1, const char *txt2, int len2) {
int sum, pos1, pos2, max;
php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max);
if ((sum = max)) { // 註:這裡是賦值並判斷,非比較
// 遞歸處理公共子串前的部分
if (pos1 && pos2) {
sum += php_similar_char(txt1, pos1, txt2, pos2);
}
// 遞歸處理公共子串後的部分
if ((pos1 + max < len1) && (pos2 + max < len2)) {
sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max,
txt2 + pos2 + max, len2 - pos2 - max);
}
}
return sum;
}
關鍵點:if ((sum = max)) 是賦值表達式,先將 max 賦給 sum,再判斷 sum 是否非零。若無公共子串(max=0),直接返回 0,遞歸終止。
PHP_FUNCTION(similar_text) {
zval **t1, **t2, **percent;
int ac = ZEND_NUM_ARGS();
int sim;
// 參數檢查:2-3 個參數
if (ac < 2 || ac > 3 || zend_get_parameters_ex(ac, &t1, &t2, &percent) == FAILURE) {
WRONG_PARAM_COUNT;
}
convert_to_string_ex(t1);
convert_to_string_ex(t2);
if (ac > 2) convert_to_double_ex(percent);
// 空字符串特殊處理
if (Z_STRLEN_PP(t1) + Z_STRLEN_PP(t2) == 0) {
if (ac > 2) Z_DVAL_PP(percent) = 0;
RETURN_LONG(0);
}
// 核心計算
sim = php_similar_char(Z_STRVAL_PP(t1), Z_STRLEN_PP(t1),
Z_STRVAL_PP(t2), Z_STRLEN_PP(t2));
// 計算百分比:匹配字符數 × 2 / (長度1 + 長度2) × 100
if (ac > 2) {
Z_DVAL_PP(percent) = sim * 200.0 / (Z_STRLEN_PP(t1) + Z_STRLEN_PP(t2));
}
RETURN_LONG(sim);
}
| 特點 | 說明 |
|---|---|
| 非標準編輯距離 | 非 Levenshtein 距離,而是基於最長公共子串分治 |
| 順序敏感 | 字符順序影響結果,"abc" vs "cba" 相似度低 |
| 性能瓶頸 | 三重循環 + 遞歸,長字符串極慢(O(n³) 級) |
| 百分比公式 | 2 × 匹配字符數 / (長度1 + 長度2) × 100% |
levenshtein() 或引入專用庫(如 text_similarity 擴展)理解底層實現有助於正確評估適用場景,避免在大數據量下誤用導致性能問題。