代码之家  ›  专栏  ›  技术社区  ›  Warren P

Delphi2010字符串(UnicodeString)是否有Boyer-Moore字符串搜索和快速搜索替换函数以及快速字符串计数?

  •  18
  • Warren P  · 技术社区  · 14 年前

    我需要三个快速的大字符串函数:快速搜索,快速搜索和替换,以及快速计数的子字符串在一个字符串。

    我已经搬走了 FastStrings

    (编辑:我有一个部分的解决方案,作为这个问题的答案,它几乎是100%完成,它甚至有一个快速字符串替换功能。我相信它一定有bug,尤其是因为它假装有Unicode功能,所以一定是因为Unicode承诺没有兑现而出现了小故障。)

    (编辑2:有趣和意想不到的结果;下面代码中可跳过的堆栈上的unicode代码点表的大堆栈大小严重影响了您在unicode字符串boyer-moore字符串搜索中可以进行的双赢优化。感谢Florent Ouchet指出我应该立即注意到的。)

    2 回复  |  直到 14 年前
        1
  •  12
  •   Warren P    7 年前

    这个答案现在是完整的,适用于区分大小写的模式,但不适用于区分大小写的模式,而且可能还有其他的错误,因为它没有经过很好的单元测试,可能会进一步优化,例如我重复了局部函数\uu SameChar,而不是使用比较函数回调,这会更快,实际上,允许用户为所有这些传递一个比较函数对于想要提供一些额外逻辑(某些语言的Unicode glyph的等价集合)的Unicode用户来说是非常好的。

    { _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; 
    

    这是一个很好的算法,因为:

    • 仅仅替换Pos(),\u FindStringBoyer()比FastCode项目人员为Delphi提供的Pos()的纯asm版本要快,后者目前用于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挑战,因为它会留下这样的足迹。我认为重复搜索是这样一种情况,上面的代码应该作为一个类来编写,skipptable应该是该类中的一个数据字段。然后可以构建一次boyer-moore表,随着时间的推移,如果字符串是不变的,则可以重复使用该对象进行快速查找。

        2
  •  2
  •   dummzeuch Stijn Sanders    10 年前

    因为我只是在找同样的东西: 我还不知道它有多好,有多快。