Существует ли поиск строки Boyer-Moore, и быстро ищите и замените функциональный и быстрый строковый счет для Строки Delphi 2010 (UnicodeString) там?

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

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

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

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

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

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

18
задан Warren P 22 July 2010 в 20:18
поделиться

1 ответ

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

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

{ _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, и если вам нужна нечувствительность к регистру, вы можете представить себе прирост производительности, когда нам не придется вызывать UpperCase на 100 мегабайтной строке. (Ладно, ваши строки не будут такими большими. Но все равно, эффективные алгоритмы - это красота.)

Хорошо, я написал String Replace в стиле Boyer-Moore:

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;

Хорошо, проблема: Stack footprint этого:

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

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

11
ответ дан 30 November 2019 в 09:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: