Нечувствительный к регистру Pos
Есть ли какая-либо сопоставимая функция, например Pos, которая не чувствительна к регистру в D2010 (unicode)?
Я знаю, что могу использовать Pos (AnsiUpperCase (FindString), AnsiUpperCase (SourceString)), но это добавляет много времени обработки, преобразовывая строки в верхний регистр каждый раз, когда вызывается функция.
Например, в цикле 1000000 Pos занимает 78 мс, а преобразование в верхний регистр занимает 764 мс.
str1 := 'dfkfkL%&/s"#<.676505';
for i := 0 to 1000000 do
PosEx('#<.', str1, 1); // Takes 78ms
for i := 0 to 1000000 do
PosEx(AnsiUpperCase('#<.'), AnsiUpperCase(str1), 1); // Takes 764ms
Я знаю, что для повышения производительности этого конкретного примера я могу преобразовать строки в верхний регистр сначала перед циклом, но причина, по которой я ищу функцию Pos-like, которая не чувствительна к регистру, заключается в замене один из FastStrings. Все строки, в которых я буду использовать Pos, будут разными, поэтому мне нужно будет преобразовать каждый из них в верхний регистр.
Есть ли какая-либо другая функция, которая может быть быстрее, чем Pos +, преобразовать строки в верхний регистр?
Ответы
Ответ 1
Эта версия моего предыдущего ответа работает как в D2007, так и в D2010.
- В Delphi 2007
CharUpCaseTable
составляет 256 байтов - В Delphi 2010 это 128 КБ (65535 * 2).
Причиной является размер Char. В более старой версии Delphi мой оригинальный код поддерживал только текущий набор символов локали при инициализации. Мой InsensPosEx
примерно в 4 раза быстрее, чем ваш код. Конечно, можно идти еще быстрее, но мы потеряли бы простоту.
type
TCharUpCaseTable = array [Char] of Char;
var
CharUpCaseTable: TCharUpCaseTable;
procedure InitCharUpCaseTable(var Table: TCharUpCaseTable);
var
n: cardinal;
begin
for n := 0 to Length(Table) - 1 do
Table[Char(n)] := Char(n);
CharUpperBuff(@Table, Length(Table));
end;
function InsensPosEx(const SubStr, S: string; Offset: Integer = 1): Integer;
var
n: Integer;
SubStrLength: Integer;
SLength: Integer;
label
Fail;
begin
Result := 0;
if S = '' then Exit;
if Offset <= 0 then Exit;
SubStrLength := Length(SubStr);
SLength := Length(s);
if SubStrLength > SLength then Exit;
Result := Offset;
while SubStrLength <= (SLength-Result+1) do
begin
for n := 1 to SubStrLength do
if CharUpCaseTable[SubStr[n]] <> CharUpCaseTable[s[Result+n-1]] then
goto Fail;
Exit;
Fail:
Inc(Result);
end;
Result := 0;
end;
//...
initialization
InitCharUpCaseTable({var}CharUpCaseTable);
Ответ 2
Встроенная функция Delphi для этого - в AnsiStrings.ContainsText для AnsiStrings и StrUtils.ContainsText для строк Unicode.
В фоновом режиме, однако, они используют логику, очень похожую на вашу логику.
Независимо от того, в какой библиотеке такие функции всегда будут медленными: особенно для того, чтобы быть максимально совместимыми с Unicode, у них должно быть довольно много накладных расходов. А поскольку они находятся внутри цикла, это дорого стоит.
Единственный способ обойти эти накладные расходы - сделать как можно больше этих преобразований вне цикла.
Итак: следуйте своему собственному предложению, и у вас есть действительно хорошее решение.
- Йерун
Ответ 3
Я также столкнулся с проблемой преобразования FastStrings, которая использовала поиск Boyer-Moore (BM), чтобы получить некоторую скорость для D2009 и D2010. Поскольку многие из моих поисков ищут только один символ, и большинство из них ищут неалфавитные символы, моя версия SmartPos D2010 имеет версию перегрузки с широким символом в качестве первого аргумента и выполняет простой цикл через строку чтобы найти их. Я использую верхний регистр обоих аргументов для обработки нескольких случайных случаев. Для моих приложений я считаю, что скорость этого решения сравнима с FastStrings.
В случае "поиска строки" мой первый проход состоял в том, чтобы использовать SearchBuf и делать верхний регистр и принимать штраф, но я недавно изучал возможность использования реализации Unicode BM. Как вам известно, BM не слишком хорошо масштабируется в кодировках Unicode, но существует реализация Unicode BM в Soft Gems. Это предписывает D2009 и D2010, но выглядит так, как если бы он конвертировался довольно легко. Автор, Майк Лишке, решает проблему с верхним регистром, включив таблицу верхних регистров Unicode 67kb, и это может быть слишком большим шагом для моих скромных требований. Так как строки поиска обычно короткие (хотя и не такие короткие, как ваш единственный трехсимвольный пример), накладные расходы для Unicode BM также могут быть ценой, которую не стоит платить: преимущество BM увеличивается с длиной поиска строки.
Это определенно ситуация, когда бенчмаркинг с некоторыми конкретными приложениями для конкретных приложений понадобится до включения этого Unicode BM в мои собственные приложения.
Изменить: некоторые базовые тесты показывают, что я был прав, чтобы быть осторожным с решением Unicode Tuned Boyer-Moore. В моей среде UTBM приводит к увеличению кода и увеличению времени. Я мог бы подумать над его использованием, если мне нужны некоторые дополнительные функции, которые предоставляет эта реализация (обработка суррогатов и поиск только целыми словами).
Ответ 4
Вот тот, который я написал и использовал в течение многих лет:
function XPos( const cSubStr, cString :string ) :integer;
var
nLen0, nLen1, nCnt, nCnt2 :integer;
cFirst :Char;
begin
nLen0 := Length(cSubStr);
nLen1 := Length(cString);
if nLen0 > nLen1 then
begin
// the substr is longer than the cString
result := 0;
end
else if nLen0 = 0 then
begin
// null substr not allowed
result := 0;
end
else
begin
// the outer loop finds the first matching character....
cFirst := UpCase( cSubStr[1] );
result := 0;
for nCnt := 1 to nLen1 - nLen0 + 1 do
begin
if UpCase( cString[nCnt] ) = cFirst then
begin
// this might be the start of the substring...at least the first
// character matches....
result := nCnt;
for nCnt2 := 2 to nLen0 do
begin
if UpCase( cString[nCnt + nCnt2 - 1] ) <> UpCase( cSubStr[nCnt2] ) then
begin
// failed
result := 0;
break;
end;
end;
end;
if result > 0 then
break;
end;
end;
end;
Ответ 5
Jedi Code Library содержит StrIPos и тысячи других полезных функций для дополнения Delphi RTL. Когда я все еще много работал в Delphi, JCL и его визуальный брат JVCL были одними из первых вещей, которые я добавил к недавно установленному Delphi.
Ответ 6
Почему бы просто не преобразовать как подстроку, так и исходную строку в нижний или верхний регистр внутри обычного оператора Pos. Результат будет эффективно нечувствителен к регистру, поскольку оба аргумента все в одном случае. Простой и легкий.
Ответ 7
Вместо "AnsiUpperCase" вы можете использовать таблицу намного быстрее.
Я изменил свой старый код. Это очень просто и очень быстро.
Проверьте это:
type
TAnsiUpCaseTable = array [AnsiChar] of AnsiChar;
var
AnsiTable: TAnsiUpCaseTable;
procedure InitAnsiUpCaseTable(var Table: TAnsiUpCaseTable);
var
n: cardinal;
begin
for n := 0 to SizeOf(TAnsiUpCaseTable) -1 do
begin
AnsiTable[AnsiChar(n)] := AnsiChar(n);
CharUpperBuff(@AnsiTable[AnsiChar(n)], 1);
end;
end;
function UpCasePosEx(const SubStr, S: string; Offset: Integer = 1): Integer;
var
n :integer;
SubStrLength :integer;
SLength :integer;
label
Fail;
begin
SLength := length(s);
if (SLength > 0) and (Offset > 0) then begin
SubStrLength := length(SubStr);
result := Offset;
while SubStrLength <= SLength - result + 1 do begin
for n := 1 to SubStrLength do
if AnsiTable[SubStr[n]] <> AnsiTable[s[result + n -1]] then
goto Fail;
exit;
Fail:
inc(result);
end;
end;
result := 0;
end;
initialization
InitAnsiUpCaseTable(AnsiTable);
end.
Ответ 8
Я думаю, что преобразование в верхний или нижний регистр перед Pos - лучший способ, но вы должны попытаться вызвать функции AnsiUpperCase/AnsiLowerCase как можно меньше.
Ответ 9
В этом случае я не смог найти какой-либо подход, который был бы даже хорош, не говоря уже лучше, чем Pos() + некоторая форма нормализации строки (преобразование в верхнем/нижнем регистре).
Это не удивительно, так как при анализе обработки строк в Unicode в Delphi 2009 я обнаружил, что процедура RT() RTL значительно улучшилась после Delphi 7, частично объясняя тот факт, что аспекты библиотек FastCode были включены в RTL в течение некоторого времени.
У библиотеки FastStrings, с другой стороны, нет - iirc - значительно обновлено в течение длительного времени. В тестах я обнаружил, что многие процедуры FastStrings на самом деле были обойдены эквивалентными функциями RTL (с несколькими исключениями, объясняемыми неизбежными накладными расходами, вызванными дополнительными осложнениями Unicode).
"Char -Wise" обработка решения, представленная Стивом, является лучшим до сих пор imho.
Любой подход, который включает в себя нормализацию целых строк (как строки, так и подстроки), рискует вносить ошибки в любую позицию на основе символов в результатах из-за того, что с помощью строк Unicode преобразование случая может привести к изменению длины строки (некоторые символы преобразуются в большее количество символов в случае преобразования кода).
Это могут быть редкие случаи, но обычная Steve позволяет избежать их и лишь на 10% медленнее, чем уже довольно быстрый Pos + Uppercase (результаты теста не совпадают с моими оценками).