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

@ user582118の答えに基づいて構築:

正規表現パターンとして使用する場合は^v(ol\.?|olume)?\s*([0-9]+)$、考えられるすべての数値を試す必要はありません。最後に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 に答える
5

多くの文字列と頻繁な検索では、接尾辞木を使用するのが最善の策です。そうでなければ、正規表現を使用するより簡単な方法も役立つ可能性があり、文字列は十分に規則的に見えます。

于 2012-07-25T21:18:27.150 に答える
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」と数値の間にピリオドまたはスペースがある場合とない場合がある文字列の比較を処理します。ピリオドと空白を削除すると、ボリューム番号ごとに 2 つの If ステートメントが残ります。1 つは VOL 用、もう 1 つは VOLUME 用です。「ボリューム」を「vol」に置き換えることで、ボリュームごとに 1 つの If ステートメントに減らすこともできます。

于 2012-07-25T23:06:00.013 に答える
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. 状態が search-txt-1 の場合、現在の文字と右側の任意の場所で txt-1 (つまり "V") を検索します ( System.StrUtils.PosEx 関数)

    1.1。見つからない場合 - ループを終了し、テキストが見つかりません

    1.2. 見つかった場合 - inc(current-number), state := search-txt-2, 次のループ

  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(現在の番号、長さ(txt-2)、状態:= 検索-txt-3、次のループ

    2.2 見つからない場合、現在の番号、状態を変更しないでください:=スキップドット、次のループ

  3. 状態が search-txt-3 の場合、上記のように txt-3 を検索します

    3.1 見つかった場合、inc(現在の番号、長さ(txt-3)、状態:=スキップドット、次のループ

    3.2 見つからない場合、現在の番号、状態を変更しないでください:=スキップドット、次のループ

  4. 状態がスキップ ドットの場合、現在の文字がドットかどうかを確認します

    4.1 である場合、inc (現在の番号)、状態 := skip-few-blanks、次のループ

    4.2 そうでない場合は現在の番号を変更しないでください、状態:=スキップ数ブランク、次のループ

  5. skip-few-blanks の場合、current-char が " " かどうかを調べます

    5.1 である場合、inc (現在の番号)、状態 := skip-few-blanks、次のループ (さらに空白がある可能性があります)

    5.2 そうでない場合は、現在の番号、状態を変更しないでください:=多分番号、次のループ

  6. たぶん番号なら System.Character.IsDigit(current-char) ???

    6.1 そうでない場合 - 番号なし、検索に失敗、次の試行 - 現在の番号を変更しない、状態 := search-txt-1、次のループ

    6.2 ある場合、number の開始位置を覚えておいてください。state := reading-number, inc (current-number), 次のループ

  7. if reading-number then System.Character.IsDigit(current-char) ???

    7.1 の場合 - もう 1 桁 - 状態 := 読み取り番号, inc (現在の番号), 次のループ

    7.2 そうでない場合 - 数値オーバー - 桁の先頭から前の文字 (最後の桁) までの文字列のスライスを取得し、それを変換 (IntToStr(Copy(string, number-start, number-length)) し、ループを終了します ( 1 つの文字列で複数の数字を検索しますか?)

より複雑な文法には、Yacc/Bison などのツールがあります。しかし、そのような単純なものについては、独自のカスタム FSM を作成することができます。それは難しいことではありませんが、最も速い方法です。非常に注意して、状態遷移と現在の文字数のシフトでエラーを起こさないようにしてください。

私が作っていないことを願っていますが、あなたはそれをテストする必要があります.

于 2012-07-26T10:32:27.977 に答える
2

検索文字列を最初に (1 回) 大文字にしてから、検索文字列の大文字バージョンに対してのみ各チェックを実行します。これにより、大文字と小文字を区別しない検索 (両方の文字列の大文字と小文字が毎回変わる可能性があります) を必要とせずに、チェックの数が半分に減ります。

さらに一歩進んで、JCL で StrMatches などのワイルドカード一致関数の 1 つを使用することもできます。ただし、これによりコードの行数は減りますが、特定の一致を取得するほど速くはなりません。

ボリュームに多くの異なる値を作成することが予想される場合は、独自の関数を作成して文字列のアルファベット部分を検索し、その後に続く数字を個別にチェックします。

于 2012-07-25T23:14:26.913 に答える