Есть ли поиск строки Бойера-Мура и быстрый поиск и замена функции и быстрый подсчет строк для строки Delphi 2010 (UnicodeString)?

Мне нужны три функции fast-on-large-strings: быстрый поиск, быстрый поиск и замена и быстрый подсчет подстрок в строке.

Я столкнулся с поиском строк Бойера-Мура в C++ и Python, но единственный алгоритм Delphi Boyer-Moore, используемый для реализации быстрого поиска и замены, который я нашел, является частью FastStrings Питера Морриса, ранее программного обеспечения DroopyEyes, и его веб-сайт и электронная почта больше не работают.

Я уже портировал FastStrings вперед, чтобы отлично работать для AnsiStrings в Delphi 2009/2010, где байт равен одному AnsiChar, но заставить их также работать со строкой (UnicodeString) в Delphi 2010 кажется нетривиальным.

используя этот алгоритм Бойера-Мура, можно легко выполнять поиск без учета регистра, а также поиск без учета регистра и замену без какой-либо временной строки (с использованием StrUpper и т. д.) и без вызова Pos (), который медленнее, чем Boyer-Moore поиск, когда требуется повторный поиск по одному и тому же тексту.

(Edit: у меня есть частичное решение, написанное как ответ на этот вопрос, оно почти на 100% завершено, у него даже есть функция быстрой замены строки. Я считаю, что у него должны быть ошибки, и особенно думаю, что, поскольку он притворяется Unicode способным, что должно быть, что есть глюки из-за невыполненных обещаний Unicode. )

(Edit2: интересный и неожиданный результат; большой размер стека unicode таблица code-point в таблице stack-SkipTable в приведенном ниже коде серьезно снижает количество беспроигрышной оптимизации, которую вы можете сделать здесь в строке unicode boyer-moore string search. Благодаря Флоран Ouchet для указывая на то, что я должна была сразу заметить.)

2 ответов


этот ответ теперь завершен и работает для режима, чувствительного к регистру, но не работает для режима, нечувствительного к регистру, и, вероятно, имеет и другие ошибки, поскольку он не хорошо протестирован и, вероятно, может быть оптимизирован дальше, например, я повторил локальную функцию _ _ SameChar вместо использования обратного вызова функции сравнения, который был бы быстрее, и на самом деле, позволяя пользователю передавать функцию сравнения для всех этих логика (эквивалентные наборы символов Юникода для некоторых языков).

основываясь на коде Дорина Доминики, я построил следующее.

{ _FindStringBoyer:
  Boyer-Moore search algorith using regular String instead of AnsiSTring, and no ASM.
  Credited to Dorin Duminica.
}
function _FindStringBoyer(const sString, sPattern: string;
  const bCaseSensitive: Boolean = True; const fromPos: Integer = 1): Integer;

    function __SameChar(StringIndex, PatternIndex: Integer): Boolean;
    begin
      if bCaseSensitive then
        Result := (sString[StringIndex] = sPattern[PatternIndex])
      else
        Result := (CompareText(sString[StringIndex], sPattern[PatternIndex]) = 0);
    end; // function __SameChar(StringIndex, PatternIndex: Integer): Boolean;

var
  SkipTable: array [Char] of Integer;
  LengthPattern: Integer;
  LengthString: Integer;
  Index: Integer;
  kIndex: Integer;
  LastMarker: Integer;
  Large: Integer;
  chPattern: Char;
begin
  if fromPos < 1 then
    raise Exception.CreateFmt('Invalid search start position: %d.', [fromPos]);
  LengthPattern := Length(sPattern);
  LengthString := Length(sString);
  for chPattern := Low(Char) to High(Char) do
    SkipTable[chPattern] := LengthPattern;
  for Index := 1 to LengthPattern -1 do
    SkipTable[sPattern[Index]] := LengthPattern - Index;
  Large := LengthPattern + LengthString + 1;
  LastMarker := SkipTable[sPattern[LengthPattern]];
  SkipTable[sPattern[LengthPattern]] := Large;
  Index := fromPos + LengthPattern -1;
  Result := 0;
  while Index <= LengthString do begin
    repeat
      Index := Index + SkipTable[sString[Index]];
    until Index > LengthString;
    if Index <= Large then
      Break
    else
      Index := Index - Large;
    kIndex := 1;
    while (kIndex < LengthPattern) and __SameChar(Index - kIndex, LengthPattern - kIndex) do
      Inc(kIndex);
    if kIndex = LengthPattern then begin
      // Found, return.
      Result := Index - kIndex + 1;
      Index := Index + LengthPattern;
      exit;
    end else begin
      if __SameChar(Index, LengthPattern) then
        Index := Index + LastMarker
      else
        Index := Index + SkipTable[sString[Index]];
    end; // if kIndex = LengthPattern then begin
  end; // while Index <= LengthString do begin
end;

{ Written by Warren, using the above code as a starter, we calculate the SkipTable once, and then count the number of instances of
  a substring inside the main string, at a much faster rate than we
  could have done otherwise.  Another thing that would be great is
  to have a function that returns an array of find-locations,
  which would be way faster to do than repeatedly calling Pos.
}
function _StringCountBoyer(const aSourceString, aFindString : String; Const CaseSensitive : Boolean = TRUE) : Integer;
var
  foundPos:Integer;
  fromPos:Integer;
  Limit:Integer;
  guard:Integer;
  SkipTable: array [Char] of Integer;
  LengthPattern: Integer;
  LengthString: Integer;
  Index: Integer;
  kIndex: Integer;
  LastMarker: Integer;
  Large: Integer;
  chPattern: Char;
    function __SameChar(StringIndex, PatternIndex: Integer): Boolean;
    begin
      if CaseSensitive then
        Result := (aSourceString[StringIndex] = aFindString[PatternIndex])
      else
        Result := (CompareText(aSourceString[StringIndex], aFindString[PatternIndex]) = 0);
    end; // function __SameChar(StringIndex, PatternIndex: Integer): Boolean;

begin
  result := 0;
  foundPos := 1;
  fromPos := 1;
  Limit := Length(aSourceString);
  guard := Length(aFindString);
  Index := 0;
  LengthPattern := Length(aFindString);
  LengthString := Length(aSourceString);
  for chPattern := Low(Char) to High(Char) do
    SkipTable[chPattern] := LengthPattern;
  for Index := 1 to LengthPattern -1 do
    SkipTable[aFindString[Index]] := LengthPattern - Index;
  Large := LengthPattern + LengthString + 1;
  LastMarker := SkipTable[aFindString[LengthPattern]];
  SkipTable[aFindString[LengthPattern]] := Large;
  while (foundPos>=1) and (fromPos < Limit) and (Index<Limit) do begin

    Index := fromPos + LengthPattern -1;
    if Index>Limit then
        break;
    kIndex := 0;
    while Index <= LengthString do begin
      repeat
        Index := Index + SkipTable[aSourceString[Index]];
      until Index > LengthString;
      if Index <= Large then
        Break
      else
        Index := Index - Large;
      kIndex := 1;
      while (kIndex < LengthPattern) and __SameChar(Index - kIndex, LengthPattern - kIndex) do
        Inc(kIndex);
      if kIndex = LengthPattern then begin
        // Found, return.
        //Result := Index - kIndex + 1;
        Index := Index + LengthPattern;
        fromPos := Index;
        Inc(Result);
        break;
      end else begin
        if __SameChar(Index, LengthPattern) then
          Index := Index + LastMarker
        else
          Index := Index + SkipTable[aSourceString[Index]];
      end; // if kIndex = LengthPattern then begin
    end; // while Index <= LengthString do begin

  end;
end; 

Это действительно хороший алгоритм, потому что:

  • это намного быстрее, чтобы подсчитать экземпляры подстроки X в строке Y таким образом, великолепно так.
  • для простой замены Pos () _FindStringBoyer () быстрее, чем чистая версия asm Pos (), внесенная в Delphi людьми проекта FastCode, то есть в настоящее время используется для Pos, и если вам нужна нечувствительность к регистру, вы можете представить себе повышение производительности, когда нам не нужно вызывать верхний регистр в строке 100 мегабайт. (Хорошо, твои струны не будут такими большими. Но все же, эффективные алгоритмы-это вещь красоты.)

хорошо, я написал замену строки в стиле Бойера-Мура:

function _StringReplaceBoyer(const aSourceString, aFindString,aReplaceString : String; Flags: TReplaceFlags) : String;
var
  errors:Integer;
  fromPos:Integer;
  Limit:Integer;
  guard:Integer;
  SkipTable: array [Char] of Integer;
  LengthPattern: Integer;
  LengthString: Integer;
  Index: Integer;
  kIndex: Integer;
  LastMarker: Integer;
  Large: Integer;
  chPattern: Char;
  CaseSensitive:Boolean;
  foundAt:Integer;
  lastFoundAt:Integer;
  copyStartsAt:Integer;
  copyLen:Integer;
    function __SameChar(StringIndex, PatternIndex: Integer): Boolean;
    begin
      if CaseSensitive then
        Result := (aSourceString[StringIndex] = aFindString[PatternIndex])
      else
        Result := (CompareText(aSourceString[StringIndex], aFindString[PatternIndex]) = 0);
    end; // function __SameChar(StringIndex, PatternIndex: Integer): Boolean;

begin
  result := '';
  lastFoundAt := 0;
  fromPos := 1;
  errors := 0;
  CaseSensitive := rfIgnoreCase in Flags;
  Limit := Length(aSourceString);
  guard := Length(aFindString);
  Index := 0;
  LengthPattern := Length(aFindString);
  LengthString := Length(aSourceString);
  for chPattern := Low(Char) to High(Char) do
    SkipTable[chPattern] := LengthPattern;
  for Index := 1 to LengthPattern -1 do
    SkipTable[aFindString[Index]] := LengthPattern - Index;
  Large := LengthPattern + LengthString + 1;
  LastMarker := SkipTable[aFindString[LengthPattern]];
  SkipTable[aFindString[LengthPattern]] := Large;
  while (fromPos>=1) and (fromPos <= Limit) and (Index<=Limit) do begin

    Index := fromPos + LengthPattern -1;
    if Index>Limit then
        break;
    kIndex := 0;
    foundAt := 0;
    while Index <= LengthString do begin
      repeat
        Index := Index + SkipTable[aSourceString[Index]];
      until Index > LengthString;
      if Index <= Large then
        Break
      else
        Index := Index - Large;
      kIndex := 1;
      while (kIndex < LengthPattern) and __SameChar(Index - kIndex, LengthPattern - kIndex) do
        Inc(kIndex);
      if kIndex = LengthPattern then begin


        foundAt := Index - kIndex + 1;
        Index := Index + LengthPattern;
        //fromPos := Index;
        fromPos := (foundAt+LengthPattern);
        if lastFoundAt=0 then begin
                copyStartsAt := 1;
                copyLen := foundAt-copyStartsAt;
        end else begin
                copyStartsAt := lastFoundAt+LengthPattern;
                copyLen := foundAt-copyStartsAt;
        end;

        if (copyLen<=0)or(copyStartsAt<=0) then begin
                Inc(errors);
        end;

        Result := Result + Copy(aSourceString, copyStartsAt, copyLen ) + aReplaceString;
        lastFoundAt := foundAt;
        if not (rfReplaceAll in Flags) then
                 fromPos := 0; // break out of outer while loop too!
        break;
      end else begin
        if __SameChar(Index, LengthPattern) then
          Index := Index + LastMarker
        else
          Index := Index + SkipTable[aSourceString[Index]];
      end; // if kIndex = LengthPattern then begin
    end; // while Index <= LengthString do begin
  end;
  if (lastFoundAt=0) then
  begin
     // nothing was found, just return whole original string
      Result := aSourceString;
  end
  else
  if (lastFoundAt+LengthPattern < Limit) then begin
     // the part that didn't require any replacing, because nothing more was found,
     // or rfReplaceAll flag was not specified, is copied at the
     // end as the final step.
    copyStartsAt := lastFoundAt+LengthPattern;
    copyLen := Limit; { this number can be larger than needed to be, and it is harmless }
    Result := Result + Copy(aSourceString, copyStartsAt, copyLen );
  end;

end;

хорошо, Проблема: стек след этого:

var
  skiptable : array [Char] of Integer;  // 65536*4 bytes stack usage on Unicode delphi

Прощай CPU ад, Привет стек ад. Если я пойду на динамический массив, затем я должен изменить его размер во время выполнения. Таким образом, это в основном быстро, потому что система виртуальной памяти на вашем компьютере не мигает при 256K в стеке, но это не всегда оптимальный фрагмент кода. Тем не менее, мой компьютер не мигает при большом стеке, как это. Это не станет стандартной библиотекой Delphi по умолчанию или выиграть любой вызов fastcode в будущем, с таким следом. Я думаю, что повторные поиски-это случай, когда вышеуказанный код должен быть записывается как класс, и skiptable должен быть полем данных в этом классе. Затем вы можете построить таблицу Бойера-Мура один раз и со временем, если строка инвариантна, повторно использовать этот объект для быстрого поиска.


Так как я просто искал то же самое: У Jedi JCL есть поисковая система unicode, использующая Boyer-Moore в jclUnicode.первенство. Я понятия не имею, насколько он хорош и насколько быстр.