不区分大小写的Pos

时间:2009-10-10 21:42:25

标签: delphi delphi-2010

在D2010(unicode)中是否有像Pos这样不区分大小写的类似功能?

我知道我可以使用Pos(AnsiUpperCase(FindString),AnsiUpperCase(SourceString))但是每次调用函数时都会通过将字符串转换为大写来增加处理时间。

例如,在1000000循环上,Pos需要78ms,而转换为大写需要764ms。

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函数的原因是要替换一个来自FastStrings。我将使用Pos的所有字符串将是不同的,所以我需要将每个字符串转换为大写。

是否还有其他功能可能比Pos +将字符串转换为大写更快?

9 个答案:

答案 0 :(得分:24)

内置的Delphi函数用于AnsiStrings AnsiStrings.ContainsText和Unicode字符串StrUtils.ContainsText

然而,在后台,他们使用的逻辑非常类似于你的逻辑。

无论在哪个库中,这样的函数总是很慢:特别是为了与Unicode兼容,它们需要有相当多的开销。由于它们位于循环内部,因此成本很高。

规避开销的唯一方法是尽可能地在循环外进行转换。

所以:按照你自己的建议,你有一个非常好的解决方案。

- 的Jeroen

答案 1 :(得分:9)

此版本的my previous answer适用于D2007和D2010。

  • 在Delphi 2007中,CharUpCaseTable为256字节
  • 在Delphi 2010中,它是128 KB(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 :(得分:5)

我还遇到了转换FastStrings的问题,它使用Boyer-Moore(BM)搜索来获得D2009和D2010的速度。由于我的许多搜索仅查找单个字符,并且其中大多数都在寻找非字母字符,因此我的D2010版本的SmartPos具有一个带有widechar作为第一个参数的重载版本,并且通过字符串进行简单的循环找到这些。我使用两个参数的大写来处理少数不区分大小写的情况。对于我的应用程序,我相信这个解决方案的速度可与FastStrings相媲美。

对于'string find'的情况,我的第一个传递是使用SearchBuf并执行大写并接受惩罚,但我最近一直在研究使用Unicode BM实现的可能性。您可能知道,BM不能很好地扩展到Unicode比例的字符集,但在Soft Gems处有一个Unicode BM实现。这个在D2009和D2010之前,但看起来好像很容易转换。作者Mike Lischke通过包含一个67kb的Unicode大写表解决了大写问题,这对于我的适度要求来说可能是一个太过分了。由于我的搜索字符串通常较短(尽管不像单个三字符示例那么短),因此Unicode BM的开销也可能是不值得付出的代价:BM优势随着搜索字符串的长度而增加。

这绝对是在将Unicode BM合并到我自己的应用程序之前需要使用一些真实的特定于应用程序的示例进行基准测试的情况。

编辑:一些基本的基准测试显示我对“Unicode Tuned Boyer-Moore”解决方案持谨慎态度是正确的。在我的环境中,UTBM会产生更大的代码,更长的时间。如果我需要这个实现提供的一些额外功能(处理代理和整个单词只搜索),我可以考虑使用它。

答案 3 :(得分: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;

答案 4 :(得分:1)

Jedi Code Library有StrIPos和数千个其他有用的函数来补充Delphi的RTL。当我在Delphi中仍然工作很多时,JCL及其视觉兄弟JVCL是我在新安装的Delphi中添加的第一件事。

答案 5 :(得分:1)

为什么不在常规Pos语句中将子字符串和源字符串转换为小写或大写。结果实际上是不区分大小写的,因为两个参数都在一个案例中。简单而精简。

答案 6 :(得分:0)

相反'AnsiUpperCase'你可以使用Table它更快。 我重塑了旧代码。它非常简单,也非常快。 检查一下:

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.

答案 7 :(得分:0)

我认为,在Pos之前转换为大写或小写是最好的方法,但是你应该尝试尽可能少地调用AnsiUpperCase / AnsiLowerCase函数。

答案 8 :(得分:0)

在这种情况下,我找不到任何方法,甚至比Pos()+某种形式的字符串规范化(大写/小写转换)更好。

这并不奇怪,因为在Delphi 2009中对Unicode字符串处理进行基准测试时我发现自Delphi 7以来Pos()RTL例程已经有了显着改进,部分原因是FastCode库的各个方面已被合并到RTL已经有一段时间了。

另一方面,FastStrings库没有 - iirc - 已经很长时间了。在测试中,我发现许多FastStrings例程实际上已经被等效的RTL函数所取代(有一些例外,由Unicode的额外复杂性引起的不可避免的开销解释)。

史蒂夫提出的解决方案的“Char-Wise”处理是迄今为止最好的imho。

任何涉及规范化整个字符串(字符串和子字符串)的方法都有可能在结果中的任何基于字符的位置引入错误,因为使用Unicode字符串进行大小写转换可能会导致长度发生变化字符串的字符串(某些字符在大小写转换中转换为更多/更少的字符)。

这些可能是罕见的情况,但史蒂夫的常规避免它们,并且比已经相当快的Pos +大写仅慢约10%(您的基准测试结果与我的基准测试结果不相符)。