Есть ли поиск строки Бойера-Мура и быстрый поиск и замена функции и быстрый подсчет строк для строки 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.первенство. Я понятия не имею, насколько он хорош и насколько быстр.