Y a-t-il une recherche de chaîne de caractères Boyer-Moore et une recherche rapide et remplacer la fonction et le compte de chaîne rapide pour la chaîne Delphi 2010 (UnicodeString) là-bas?

j'ai besoin de trois rapide sur les grandes chaînes de caractères fonctions: recherche rapide, rapide de recherche et de remplacement, et rapide, le nombre de sous-chaînes dans une chaîne de caractères.

j'ai fait des recherches de chaînes de caractères Boyer-Moore en C++ et en Python, mais le seul algorithme Delphi Boyer-Moore utilisé pour implémenter fast search et replace que j'ai trouvé fait partie des Fasttrings de Peter Morris, anciennement de DroopyEyes software, et son site web et e-mail ne fonctionnent plus.

j'ai déjà porté Fasttrings avant de travailler grand pour les inscriptions à Delphi 2009/2010, où un octet est égal à un AnsiChar, mais les faire fonctionner aussi avec la chaîne (UnicodeString) dans Delphi 2010 semble non-trivial.

en utilisant cet algorithme de Boyer-Moore, il devrait être possible de faire facilement des recherches insensibles à la casse, ainsi que des recherches insensibles à la casse et remplacer, sans aucune chaîne temporaire (en utilisant strupper etc), et sans appeler Pos() ce qui est plus lent que la recherche de Boyer-Moore lorsque des recherches répétées sur le même texte sont nécessaires.

(Edit: j'ai une solution partielle, écrite en réponse à cette question, elle est presque complète à 100%, elle a même une fonction de remplacement de chaîne rapide. Je pense qu'il doit y avoir des bugs, et surtout qu'étant donné qu'il prétend être capable D'Unicode, il doit y avoir des problèmes dus à des promesses non tenues D'Unicode. )

(Edit2: intéressant et résultat inattendu; la Grande Taille de pile d'une table de point de code unicode sur la pile-SkipTable dans le code ci-dessous met un amortisseur sérieux sur le montant de win-win-optimisation que vous pouvez faire ici dans une recherche de chaîne de caractères unicode boyer-moore. Merci à Florent Ouchet pour avoir souligné ce que j'aurais dû remarquer immédiatement.)

18
demandé sur Warren P 2010-07-22 20:03:36

2 réponses

cette réponse est maintenant complète et fonctionne pour le mode sensible à la casse, mais ne fonctionne pas pour le mode insensible à la casse, et a probablement d'autres bugs aussi, car il n'est pas bien unité testé, et pourrait probablement être optimisé davantage, par exemple, j'ai répété la fonction locale __SameChar au lieu d'utiliser une fonction de comparaison callback qui aurait été plus rapide, et en fait, permettant à l'utilisateur de passer dans une fonction de comparaison pour tous ceux-ci serait grand pour les utilisateurs Unicode qui veulent fournir certains logique supplémentaire (ensembles équivalents de glyphes Unicode pour certaines langues).

basé sur le code de Dorin Dominica, j'ai construit ce qui suit.

{ _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; 

c'est vraiment un bon algorithme, parce que:

  • c'est beaucoup plus rapide de compter les instances de soustring X dans string Y de cette façon, magnifiquement ainsi.
  • pour le simple remplacement de Pos () le _FindStringBoyer () est plus rapide que la version asm pure de Pos() contribué à Delphi par FastCode project people, qui est actuellement utilisé pour Pos, et si vous avez besoin de l'insensibilité cas, vous pouvez imaginer la performance boost quand nous n'avons pas à appeler UpperCase sur une chaîne de 100 mégaoctets. (OK, tes ficelles ne seront pas si grandes. Mais malgré tout, les algorithmes efficaces sont une chose de beauté.)

OK j'ai écrit une chaîne de remplacement dans le style 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;

OK, problème: empreinte de pile de ceci:

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

adieu CPU hell, Bonjour stack hell. Si j'opte pour un tableau dynamique, alors je dois le redimensionner à l'exécution. Donc cette chose est fondamentalement rapide, parce que le système de mémoire virtuelle sur votre ordinateur ne clignote pas à 256K aller sur la pile, mais ce n'est pas toujours un morceau optimal de code. Néanmoins, mon PC ne cligne pas des yeux à ce genre de choses. Il ne va pas devenir un Delphi bibliothèque standard par défaut ou gagner n'importe quel défi fastcode dans le futur, avec cette empreinte de pas. Je pense que les recherches répétées sont un cas où le code ci-dessus devrait être écrit comme une classe, et le skiptable devrait être un champ de données dans cette classe. Ensuite, vous pouvez construire la table boyer-moore une fois, et au fil du temps, si la chaîne est invariante, utilisez à plusieurs reprises cet objet pour faire des recherches rapides.

11
répondu Warren P 2018-04-06 21:41:46

puisque je cherchais juste la même chose: Jedi JCL a un moteur de recherche unicode aware utilisant Boyer-Moore en jclUnicode.PA. Je ne sais pas encore à quel point c'est bon ou rapide.

1
répondu dummzeuch 2014-09-14 17:40:43