Нечувствительный к регистру 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 (результаты теста не совпадают с моими оценками).