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.)
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.
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.