[英]How to search for many variations of a substring in string
我試圖在一個字符串中搜索一個子字符串,但是必須有一個更有效的方法然后這個..
//search for volume
if AnsiContainsStr(SearchString, 'v1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'V1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'Volume1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'Volume 1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'Vol1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'vol1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'Vol 1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'vol 1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'Vol.1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'vol.1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'Vol. 1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'vol. 1') then
Volume := '1';
if AnsiContainsStr(SearchString, 'v2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'V2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'Volume2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'Volume 2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'Vol2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'vol2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'Vol 2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'vol 2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'Vol.2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'vol.2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'Vol. 2') then
Volume := '2';
if AnsiContainsStr(SearchString, 'vol. 2') then
Volume := '2';
由於您使用XE2對其進行了標記,因此您可以使用正則表達式輕松進行匹配
var
Regex: String;
begin
Regex := '^[v](ol\.?|olume)?\s*(1|\.\s*1)$';
if TRegEx.IsMatch(SearchString, Regex, [roIgnoreCase]) then
Volume := '1'
Regex := '^[v](ol\.?|olume)?\s*(2|\.\s*2)$';
if TRegEx.IsMatch(SearchString, Regex, [roIgnoreCase]) then
Volume := '2'
end;
現在,我不是最好的設計正則表達式,但我測試了上面的那個,它似乎匹配你所有的變化(也許其他人可以想出一個更簡潔的)。
對於很多字符串和頻繁搜索,使用后綴樹將是您最好的選擇。 否則使用正則表達式的更簡單方法也可以提供幫助,您的字符串看起來足夠規
建立在@ user582118的答案上:
如果使用^v(ol\\.?|olume)?\\s*([0-9]+)$
作為RegEx模式,則不必嘗試每個可能的數值。 它將與最后的一個或多個數字字符匹配。 然后,您可以使用TMatch
“ Value
和Groups
屬性從字符串中提取數字。
var
RegEx: TRegEx; // This is a record, not a class, and doesn't need to be freed!
Match: TMatch;
i: Integer;
begin
RegEx := TRegEx.Create('^v(ol\.?|olume)?\s*([0-9]+)$');
Match := RegEx.Match('vol.3456');
WriteLn('Value: ' + Match.Value);
for i := 0 to Match.Groups.Count - 1 do
WriteLn('Group', i, ': ', Match.Groups[i].Value);
end;
得到:
Value: vol.3456
Group0: vol.3456
Group1: ol.
Group2: 3456
嘗試這樣的事情:
const
Prefixes: array[0..6] of String = (
'VOLUME '
'VOLUME'
'VOL. '
'VOL '
'VOL.'
'VOL'
'V'
);
var
S: String;
P: PChar;
I, J, Len: Integer;
Volume: Char;
begin
Volume = #0;
S := UpperCase(SearchString);
P := PChar(S);
Len := Length(S);
I := 1;
while (Len > 0) and (Volume = #0) do
begin
if (P^ <> 'V') then begin
Inc(P);
Dec(Len);
Continue;
end;
for J := Low(Prefixes) to High(Prefixes) do
begin
if AnsiStrLComp(P, PChar(Prefixes[J]), Length(Prefixes[J])) = 0 then
begin
Inc(P, Length(Prefixes[J]));
Dec(Len, Length(Prefixes[J]));
if (Len > 0) then begin
if (P^ >= '1') and (P^ <= '7') then
Volume := P^;
end;
Break;
end;
end;
end;
end;
為了比較郵件地址,我不得不做類似的事情。 我刪除了空格和標點符號。 然后我使用了CompareText,因此它不區分大小寫。
很多你的If語句都涉及比較可能有或沒有“Vol”或“Volume”之間的句號或空格的字符串和數字。 刪除句點和空格,每個卷號留下兩個If語句:一個用於VOL,一個用於VOLUME。 您甚至可以通過將“volume”替換為“vol”來將每個卷減少到一個If語句。
首先使您的搜索字符串大寫(一次),然后針對搜索字符串的大寫版本執行每個檢查。 這樣可以將檢查次數減少一半而不需要不區分大小寫的搜索(這可能會每次都改變兩個字符串的大小寫)。
您可以更進一步,使用JCL中的一個通配符匹配函數,例如StrMatches。 然而,雖然這會減少代碼行數,但它不能像具有特定匹配一樣快。
如果您希望為Volume創建許多不同的值,請編寫自己的函數以搜索字符串的字母部分,然后單獨檢查后面的數字。
如果你想要它容易但很慢 - 去RegExp方式。
如果您想要快速,請閱讀@LeleDumbo的回答。
但! 在真正的搜索之前使字符串全部大寫 - AnsiUpperCase函數。 不區分大小寫的搜索會減慢每個字符的速度。 最好是復制字符串和搜索模式。 (哦,@ RobMcDonell已告訴你:-))
您要將前綴轉換為樹。 好的,在這個簡單的例子中它將適合列表(數組):“V”,“OL”,“UME” 在更復雜的情況下你可以用相同的開始搜索V-OL-UME或V-ER-SION和分裂的尾巴)
然后閱讀http://en.wikipedia.org/wiki/Finite-state_machine - 這就是你必須要做的事情。
一個簡單的草案(不涵蓋所有可能的用例,例如“Vol.2.2”)將是:
從search-txt-1狀態開始,#1 char來查看。 在每個循環中,您有當前狀態和當前要考慮的字符數(想到左邊已經掃描過的所有字符):
如果state是search-txt-1,則在當前字符和右邊任意位置搜索txt-1(即“V”)(System.StrUtils.PosEx函數)
1.1。 如果未找到 - 退出循環,則找不到文本
1.2。 如果找到 - inc(當前數字),則:= search-txt-2,下一個循環
如果state是search-txt-2,則僅搜索當前字符的txt-2(“UM”)! (懶惰:System.Copy(txt,current-char,system.length(txt-2))= txt-2; fast:與Jedi CodeLibrary的長度和偏移量的特殊比較)
2.1如果找到,inc(當前數字,長度(txt-2),狀態:= search-txt-3,下一循環
2.2如果沒有找到,請不要更改當前編號,狀態:= skip-dot,next loop
如果state是search-txt-3,那么就像上面一樣搜索txt-3
3.1 if found,inc(current-number,length(txt-3),state:= skip-dot,next loop
3.2如果沒有找到,請不要更改當前編號,狀態:=跳過點,下一個循環
如果state是skip-dot,看看current-char是否為dot
4.1如果是,inc(current-number),state:= skip-few-blanks,next loop
4.2如果不是不改變當前數字,則表示:= skip-few-blanks,next loop
如果skip-few-blanks則查看current-char是否為“”
5.1如果是,inc(current-number),state:= skip-few-blanks,next loop(可能有更多空格)
5.2如果不是不改變當前數字,則:= maybe-number,next loop
if maybe-number然后是System.Character.IsDigit(current-char)???
6.1如果不是 - 沒有數字,搜索失敗,下次嘗試 - 不要改變當前號碼,狀態:= search-txt-1,下一個循環
6.2如果是,記住數字開始的地方,狀態:= reading-number,inc(當前數字),下一個循環
如果讀取數字然后System.Character.IsDigit(current-char)???
7.1如果是 - 再多一位 - 狀態:=讀數,inc(當前數),下一循環
7.2如果不是 - 數字超過 - 從數字開始到前一個字符(最后一個數字)獲取字符串片段,轉換它(IntToStr(復制(字符串,數字 - 開始,數字長度))並退出循環(你不在一個字符串中搜索幾個數字,對嗎?)
對於更復雜的語法,有像Yacc / Bison這樣的工具。 但是對於這么簡單的你可以選擇你自己的自定義FSM,它並不難,但最快的方式。 只是要非常注意,不要在狀態轉換和當前字符數字轉換中出錯。
我希望我沒有,但你必須測試它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.