2

我正在尝试在字符串中搜索子字符串,但认为必须有比这更有效的方法..

      //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';
4

7 回答 7

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;

现在,我不是最擅长设计正则表达式,但我测试了上面的那个,它似乎匹配你所有的变体(也许其他人可以想出一个更简洁的)。

于 2012-07-26T01:08:30.910 回答
5

对于大量字符串和频繁搜索,使用后缀树是最好的选择。否则,使用正则表达式的更简单方法也会有所帮助,您的字符串看起来足够规则。

于 2012-07-25T21:18:27.150 回答
5

基于@user582118 的回答:

如果您使用^v(ol\.?|olume)?\s*([0-9]+)$RegEx 模式,则不必尝试每个可能的数值。它将在末尾匹配 1 个或多个数字字符。然后,您可以使用TMatch'sValueGroups属性从字符串中提取数字。

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
于 2012-07-26T06:10:15.107 回答
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;
于 2012-07-25T23:58:57.897 回答
3

我不得不做一次类似的事情来比较邮寄地址。我去掉了空格和标点符号。然后我使用了 CompareText,所以它不区分大小写。

您的许多 If 语句处理比较字符串,这些字符串在“Vol”或“Volume”与数字之间可能有也可能没有句点或空格。删除句点和空格,每个卷号会留下两条 If 语句:一条用于 VOL,一条用于 VOLUME。您甚至可以通过将“volume”替换为“vol”来将其缩减为每卷一个 If 语句。

于 2012-07-25T23:06:00.013 回答
2

首先将搜索字符串设为大写(一次),然后仅针对搜索字符串的大写版本进行每次检查。这将检查次数减少了一半,而不需要不区分大小写的搜索(这可能每次都会改变两个字符串的大小写)。

您可以更进一步,使用 JCL 中的通配符匹配函数之一,例如 StrMatches。然而,虽然这会减少代码行数,但它的速度不如特定匹配。

如果您希望为 Volume 创建许多不同的值,请编写您自己的函数来搜索字符串的字母部分,然后单独检查它后面的数字。

于 2012-07-25T23:14:26.913 回答
2

如果你想要它简单但缓慢 - 使用 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 字符。在每个循环中,您都有当前状态和当前要考虑的字符数(考虑到左侧已经扫描):

  1. 如果 state 为 search-txt-1,则在当前字符和右侧任意位置搜索 txt-1(即“V”)(System.StrUtils.PosEx 函数)

    1.1。如果未找到 - 退出循环,未找到文本

    1.2. 如果找到 - inc(current-number), state := search-txt-2, next loop

  2. 如果状态为 search-txt-2,则仅在当前字符处搜索 txt-2 ("UM")!(lazy: System.Copy(txt, current-char, system.length(txt-2)) = txt-2; fast: 与 Jedi CodeLibrary 的长度和偏移量的特殊比较)

    2.1 如果找到, inc(current-number, length(txt-2), state := search-txt-3, next loop

    2.2 如果未找到,请勿更改当前编号、状态 := 跳过点、下一个循环

  3. 如果 state 是 search-txt-3,然后像上面一样搜索 txt-3

    3.1 如果找到,inc(current-number, length(txt-3), state := skip-dot, next loop

    3.2 如果未找到,请勿更改当前编号、状态 := 跳过点、下一个循环

  4. 如果 state 是 skip-dot,查看 current-char 是否是 dot

    4.1 如果是, inc (current-number), state := skip-few-blanks, next loop

    4.2 如果不是,请不要更改当前编号,状态:= 跳过少数空白,下一个循环

  5. 如果跳过几个空白然后查看当前字符是否为“”

    5.1 如果是,inc (current-number), state := skip-few-blanks, next loop(可能有更多的空白)

    5.2 如果不是,请不要更改当前编号,状态:= 可能编号,下一个循环

  6. 如果可能是数字,那么 System.Character.IsDigit(current-char) ???

    6.1 如果不是 - 没有号码,搜索失败,下一次尝试 - 不要更改当前号码,状态 := search-txt-1,下一个循环

    6.2 if is,记住number从哪里开始,state := reading-number, inc (current-number), next loop

  7. 如果读取数字然后 System.Character.IsDigit(current-char) ???

    7.1 如果是 - 多一位 - 状态 := reading-number, inc (current-number), next loop

    7.2 如果不是 - number over - 获取从数字开始到前一个字符(最后一个数字)的字符串切片,转换它(IntToStr(Copy(string, number-start, number-length)) 并退出循环(你不在一个字符串中搜索多个数字,是吗?)

对于更复杂的语法,有 Yacc/Bison 之类的工具。但是对于这样一个简单的方法,您可以制作自己的自定义 FSM,这并不难,但却是最快的方法。只是要非常专心,不要在状态转换和当前字符数转换中出错。

我希望我没有做,但你必须测试它。

于 2012-07-26T10:32:27.977 回答