我正在尝试在一个字符串中搜索一个子字符串,但是必须要有一个更有效的方法呢...
//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';
答案 0 :(得分:11)
由于您使用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;
现在,我不是最擅长设计正则表达式,但我测试了上面的那个,它似乎与你所有的变化相匹配(也许其他人可以想出一个更简洁的表达式)。
答案 1 :(得分:5)
对于很多字符串和频繁搜索,使用后缀树将是您最好的选择。否则,使用正则表达式的更简单方法也可以提供帮助,您的字符串看起来足够规则。
答案 2 :(得分:5)
基于@ user582118的回答:
如果使用^v(ol\.?|olume)?\s*([0-9]+)$
作为RegEx模式,则不必尝试每个可能的数值。它将与最后的一个或多个数字字符匹配。然后,您可以使用TMatch
的{{1}}和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;
答案 3 :(得分:4)
尝试这样的事情:
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;
答案 4 :(得分:3)
我必须做一次类似的事情来比较邮寄地址。我删除了空格和标点符号。然后我使用了CompareText,因此它不区分大小写。
很多你的If语句涉及比较可能有或没有“Vol”或“Volume”之间的句号或空格的字符串和数字。删除句点和空格,每个卷号留下两个If语句:一个用于VOL,一个用于VOLUME。您甚至可以通过将“volume”替换为“vol”来将每个音量降低到一个If语句。
答案 5 :(得分:2)
首先将搜索字符串设置为大写(一次),然后针对搜索字符串的大写版本执行每项检查。这样可以将检查次数减少一半而不需要不区分大小写的搜索(这可能会每次更改两个字符串的大小写)。
您可以更进一步,使用JCL中的一个通配符匹配函数,例如StrMatches。但是,虽然这会减少代码行数,但它不能像具有特定匹配项一样快。
如果您希望为Volume创建许多不同的值,请编写自己的函数来搜索字符串的字母部分,然后单独检查后面的数字。
答案 6 :(得分:2)
如果你想要它很容易但又很慢 - 去RegExp方式。
如果您想要快速,请阅读@LeleDumbo的回答。
BUT! 在真正的搜索之前使字符串全部大写 - 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。 if found - inc(current-number),state:= search-txt-2,next loop
如果state是search-txt-2,则仅搜索当前字符的txt-2(“UM”)! (懒惰:System.Copy(txt,current-char,system.length(txt-2))= txt-2; fast:与Jedi CodeLibrary的长度和偏移量进行特殊比较)
2.1 if found,inc(current-number,length(txt-2),state:= search-txt-3,next loop
2.2如果没有找到,请不要更改当前号码,状态:=跳过点,下一个循环
如果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如果是 - 再多一位 - 状态:= reading-number,inc(当前数字),下一循环
7.2如果不是 - 数字超过 - 从数字开始到前一个字符(最后一个数字)获取字符串片段,转换它(IntToStr(复制(字符串,数字开始,数字长度))并退出循环(你没有在一个字符串中搜索多个数字,对吗?)
对于更复杂的语法,有像Yacc / Bison这样的工具。 但是对于这么简单的你可以选择你自己的自定义FSM,它并不难,但最快的方式。只是要非常注意,不要在状态转换和当前字符数字转换中出错。
我希望我没有做,但你必须测试它。