【问题标题】:delphi - strip out all non standard text characers from stringdelphi - 从字符串中删除所有非标准文本字符
【发布时间】:2011-04-13 14:00:55
【问题描述】:

我需要从字符串中删除所有非标准文本字符。我需要删除所有非 ascii 和控制字符(换行符/回车符除外)。

【问题讨论】:

    标签: delphi parsing ascii delphi-2010 delphi-7


    【解决方案1】:

    这是 Cosmin 的一个变体,它只遍历字符串一次,但使用了一种高效的分配模式:

    function StrippedOfNonAscii(const s: string): string;
    var
      i, Count: Integer;
    begin
      SetLength(Result, Length(s));
      Count := 0;
      for i := 1 to Length(s) do begin
        if ((s[i] >= #32) and (s[i] <= #127)) or (s[i] in [#10, #13]) then begin
          inc(Count);
          Result[Count] := s[i];
        end;
      end;
      SetLength(Result, Count);
    end;
    

    【讨论】:

    • 非常好的变体,只有一次重新分配,如果字符串不包含非 ASCII 字符,则可能没有重新分配。
    • var l, i, 计数:整数;开始 l := 长度;设置长度(结果,升);如果 l = 0 则退出;计数:= 0; for i := 1 to l do begin if ((s[i] >= #32) and (s[i] Count 然后 SetLength(Result, Count);结束;
    【解决方案2】:

    应该这样做:

    // For those who need a disclaimer: 
    // This code is meant as a sample to show you how the basic check for non-ASCII characters goes
    // It will give low performance with long strings that are called often.
    // Use a TStringBuilder, or SetLength & Integer loop index to optimize.
    // If you need really optimized code, pass this on to the FastCode people.
    function StripNonAsciiExceptCRLF(const Value: AnsiString): AnsiString;
    var
      AnsiCh: AnsiChar;
    begin
      for AnsiCh in Value do
        if (AnsiCh >= #32) and (AnsiCh <= #127) and (AnsiCh <> #13) and (AnsiCh <> #10) then
          Result := Result + AnsiCh;
    end;
    

    对于UnicodeString,你可以做类似的事情。

    【讨论】:

    • 我不会一遍又一遍地重新分配结果。
    • 如果速度成为问题,我会修复它。
    • 有两个潜在的问题:1)速度2)内存碎片。如果有时调用该函数并且使用小字符串,则不会成为问题。如果经常使用大字符串调用该函数,则可能会成为其中之一。像往常一样,优化需要了解某些代码的预期工作位置。
    • @David:对你来说这是微不足道的,对我来说这是微不足道的,但对于很多 SO 读者来说,这并不是微不足道的。这是帕累托原则的经典例子。我教软件开发人员作为我生活的一部分,并且经常看到 80/20 规则。因此,我的样本应该被很多人理解,需要优化的人会自己解决这个问题。我可以理解您以不同的方式看到这一点,但我认为基于一个代码示例评论“马虎的程序员”是一种苛刻的方式,特别是因为没有涉及二次沟通。
    • @JeroenWiertPluimers 过早的微优化和担心语言抽象之下的技术细节似乎是许多 Delphi 开发人员的不幸特征(尽管我不知道它在哪里或为什么成为文化的一部分) .因此,我觉得你关于首先编写干净、清晰的代码并且只在必要时进行优化(通常是在分析之后)的教训比你关于从字符串中剥离字符的说明更重要!
    【解决方案3】:

    如果您不需要就地执行此操作,而是生成字符串的副本,请尝试此代码

     type CharSet=Set of Char;
    
     function StripCharsInSet(s:string; c:CharSet):string;
      var i:Integer;
      begin
         result:='';
         for i:=1 to Length(s) do
           if not (s[i] in c) then 
             result:=result+s[i];
      end;  
    

    并像这样使用它

     s := StripCharsInSet(s,[#0..#9,#11,#12,#14..#31,#127]);
    

    编辑:为 DEL ctrl char 添加了 #127。

    EDIT2:这是一个更快的版本,感谢 ldsandon

     function StripCharsInSet(s:string; c:CharSet):string;
      var i,j:Integer;
      begin
         SetLength(result,Length(s));
         j:=0;
         for i:=1 to Length(s) do
           if not (s[i] in c) then 
            begin
             inc(j);
             result[j]:=s[i];
            end;
         SetLength(result,j);
      end;  
    

    【讨论】:

    • 对于 Delphi 2010,使用 CharInSet 函数而不是 Ch in ... 构造。
    • 别担心;您的解决方案将正常工作。不过,对于非 Ascii 字符,CharInSet 函数是必需的。
    • 非常慢,它会一遍又一遍地重新分配结果。我将结果设置为与原始字符串相同的长度,而不是在处理后设置实际长度
    【解决方案4】:

    这是一个不通过逐个字符附加来构建字符串的版本,而是一次性分配整个字符串。它需要遍历字符串两次,一次计算“好”字符,一次有效复制这些字符,但这是值得的,因为它不会进行多次重新分配:

    function StripNonAscii(s:string):string;
    var Count, i:Integer;
    begin
      Count := 0;
      for i:=1 to Length(s) do
        if ((s[i] >= #32) and (s[i] <= #127)) or (s[i] in [#10, #13]) then
          Inc(Count);
      if Count = Length(s) then
        Result := s // No characters need to be removed, return the original string (no mem allocation!)
      else
        begin
          SetLength(Result, Count);
          Count := 1;
          for i:=1 to Length(s) do
            if ((s[i] >= #32) and (s[i] <= #127)) or (s[i] in [#10, #13]) then
            begin
              Result[Count] := s[i];
              Inc(Count);
            end;
        end;
    end;
    

    【讨论】:

    • 为什么会有人反对这个?没关系,只是好奇。
    • 我不会使用 StringOfChar,而只是使用 SetLength(),无论如何都不是投反对票的理由,尽管它需要两次遍历字符串。
    • 它确实需要遍历字符串两次,但它保证最佳分配。如果对多对多字符串执行此操作,则最佳分配将比仅遍历字符串一次更重要。
    • 编辑了答案以使用 SetLength 并实现一个微小的优化,允许例程使用零或 1 个字符串分配来完成工作。
    • @Cosmin 多次遍历的一个缺点是这段代码有两个相同的 if 语句,违反了 DRY
    【解决方案5】:

    我的性能解决方案;

    function StripNonAnsiChars(const AStr: String; const AIgnoreChars: TSysCharSet): string;
    var
      lBuilder: TStringBuilder;
      I: Integer;
    begin
      lBuilder := TStringBuilder.Create;
      try
        for I := 1 to AStr.Length do
          if CharInSet(AStr[I], [#32..#127] + AIgnoreChars) then
            lBuilder.Append(AStr[I]);
        Result := lBuilder.ToString;
      finally
        FreeAndNil(lBuilder);
      end;
    end;
    

    我用delphi xe7写的

    【讨论】:

      【解决方案6】:

      我的 Result 字节数组版本:

      界面

      type
        TSBox = array of byte;
      

      和功能:

      function StripNonAscii(buf: array of byte): TSBox;
      var temp: TSBox;
          countr, countr2: integer;
      const validchars : TSysCharSet = [#32..#127];
      begin
      if Length(buf) = 0 then exit;
      countr2:= 0;
      SetLength(temp, Length(buf)); //setze temp auf länge buff
      for countr := 0 to Length(buf) do if CharInSet(chr(buf[countr]), validchars) then
        begin
          temp[countr2] := buf[countr];
          inc(countr2); //count valid chars
        end;
      SetLength(temp, countr2);
      Result := temp;
      end;
      

      【讨论】:

        猜你喜欢
        • 2010-12-04
        • 1970-01-01
        • 2017-05-09
        • 2014-04-26
        • 2013-03-12
        • 1970-01-01
        • 2022-07-06
        • 1970-01-01
        • 2011-10-19
        相关资源
        最近更新 更多