Delphi 標準庫缺乏通配符模式匹配函數。本文提供一個獨立的 IsLike(Pattern, Text) 函數,支援 ? (單字元) 與 * (多字元) 通配符,採用遞迴回溯演算法正確處理複雜模式 (如 *a*b*c*、?*?)。
function IsLike(const Pattern, Text: string): Boolean;
var
pIdx, tIdx: Integer;
pLen, tLen: Integer;
starPos, matchPos: Integer;
begin
// 統一轉小寫進行不區分大小寫比較
// 若需區分大小寫,移除 LowerCase
var Pat := LowerCase(Pattern);
var Txt := LowerCase(Text);
pLen := Length(Pat);
tLen := Length(Txt);
pIdx := 1;
tIdx := 1;
starPos := 0; // 記錄 Pattern 中最後一個 * 的位置
matchPos := 0; // 記錄 Text 中對應 * 匹配的起始位置
while tIdx <= tLen do
begin
// 情況 1: 字元匹配 或 ? 匹配任意單字元
if (pIdx <= pLen) and ((Pat[pIdx] = Txt[tIdx]) or (Pat[pIdx] = '?')) then
begin
Inc(pIdx);
Inc(tIdx);
end
// 情況 2: 遇到 *,記錄位置並假設匹配 0 字元
else if (pIdx <= pLen) and (Pat[pIdx] = '*') then
begin
starPos := pIdx;
matchPos := tIdx;
Inc(pIdx); // * 本身不消耗 Text 字元,移至 Pattern 下一字元
end
// 情況 3: 失配但之前有 *,回溯:讓 * 多吞一個字元
else if starPos > 0 then
begin
// 回溯到 * 位置
pIdx := starPos + 1;
// * 多匹配一個字元
Inc(matchPos);
tIdx := matchPos;
end
// 情況 4: 失配且無 * 可回溯,匹配失敗
else
begin
Result := False;
Exit;
end;
end;
// Text 已處理完,檢查 Pattern 剩餘是否全為 *
while (pIdx <= pLen) and (Pat[pIdx] = '*') do
Inc(pIdx);
Result := (pIdx > pLen);
end;
procedure TestIsLike;
var
Tests: array of record
Pattern, Text: string;
Expected: Boolean;
end;
I: Integer;
begin
Tests := [
('abc', 'abc', True),
('abc', 'abcd', False),
('a?c', 'abc', True),
('a?c', 'ac', False),
('a*c', 'abc', True),
('a*c', 'ac', True),
('a*c', 'axxxc', True),
('*abc*', '123abc456', True),
('*abc*', 'abc', True),
('*a*b*c*', '1a2b3c4', True),
('*a*b*c*', 'abc', True),
('?*?', 'a', True),
('?*?', 'ab', True),
('?*?', '', False),
('*.txt', 'readme.txt', True),
('*.txt', 'readme.doc', False),
('file?.log', 'file1.log', True),
('file?.log', 'file12.log', False),
('', '', True),
('*', '', True),
('?', '', False)
];
for I := 0 to High(Tests) do
begin
var Res := IsLike(Tests[I].Pattern, Tests[I].Text);
if Res <> Tests[I].Expected then
WriteLn(Format('FAIL: Pattern="%s" Text="%s" Got=%s Exp=%s',
[Tests[I].Pattern, Tests[I].Text, BoolToStr(Res), BoolToStr(Tests[I].Expected)]))
else
WriteLn(Format('OK: "%s" =~ "%s"', [Tests[I].Pattern, Tests[I].Text]));
end;
end;
採用雙指標 + 回溯點記錄:
pIdx 掃描 Pattern,tIdx 掃描 Text* 記錄 starPos (Pattern 位置) 與 matchPos (Text 位置),假設 * 匹配 0 字元,繼續匹配後續starPos > 0,回溯:* 多吞一個字元 (matchPos++),重試* 才算成功* 回溯),典型 O(m+n)Integer 索引上限 2GB)function IsLikeEx(const Pattern, Text: string): Boolean;
// 在情況 1 擴展:
// if Pat[pIdx] = '[' then
// 解析字元類別,檢查 Txt[tIdx] 是否在集合中
// 支援 [abc]、[a-z]、[^abc] 否定
// 找到對應 ']' 後繼續
// ...
end;
| 方案 | 優點 | 缺點 |
|---|---|---|
| 本函數 IsLike | 零依賴、原始碼可控、效能佳、支援 ? * | 不支援正則進階功能 (分組、前瞻、量詞) |
| TRegEx (System.RegularExpressions) | 完整 PCRE 正則、ICU 支援 Unicode | 編譯開銷大、體積大、學習曲線陡 |
| PathMatchSpec (Shlwapi.dll) | 系統原生、支援 ; 分隔多模式 | 需動態載入 DLL、行為版本相關 |
| WildcardToRegex 轉換 | 復用正則引擎 | 轉換邏輯易出錯、效能損耗 |
IsLike 以 30 行核心程式碼實作了工業級通配符匹配,適合檔案名過濾、日誌關鍵字搜尋、簡單規則引擎等場景。若需正則表達式完整功能,請直接使用 TRegEx;但對於單純 ?/* 模式,本函數零依賴、高效能、易整合,是最佳選擇。