Delphi - 从字符串中删除所有非标准文本字符

17

我需要从字符串中去除所有非标准文本字符。除了换行和回车符外,我需要删除所有非 ASCII 和控制字符。

6个回答

24

以下是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;

2
非常好的变体,只需要一次重新分配,如果字符串不包含任何非ASCII字符,则可能不需要重新分配。 - Cosmin Prund
var l, i, Count: Integer; begin l := Length(s); SetLength(Result, l); if l = 0 then Exit; Count := 0; for i := 1 to l 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; if l <> Count then SetLength(Result, Count); end; - Zam

16

应该用类似这样的代码:

// 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,您可以执行类似的操作。


6
我不会反复重新分配结果。 - user160694
3
如果速度成为问题,我会解决它。 - Jeroen Wiert Pluimers
2
@David:哇,你今天对我真是苛刻。首先,这是一个展示如何进行正确比较的代码示例。优化会分散注意力。此外,过早地进行优化会导致很多问题。这就是为什么当性能确实成为问题时,我才会优化代码。我在代码中添加了一些注释来警告,但对我来说,这些警告适用于大多数基本算法的示例代码。 - Jeroen Wiert Pluimers
3
@David:对你来说这很简单,对我来说也很简单,但是对许多 SO 读者来说,这并不简单。这是帕累托原理的经典例子。我有一部分职业是教授软件开发人员,我经常看到 80/20 规则。因此,我的示例旨在让许多人理解,并且需要进行优化的人将自己找出来。我可以理解你认为情况不同,但是我认为基于一个代码示例就评论“粗心的程序员”太过苛刻,特别是因为没有二次交流的情况下。 - Jeroen Wiert Pluimers
4
许多Delphi开发人员都存在过早微观优化和过分关注语言抽象层下的技术细节等不幸特征(虽然我不知道它是如何成为文化的)。因此,我认为你有关首先编写清晰易懂的代码,仅在必要时进行优化(通常在分析后进行)的课程比你关于从字符串中删除字符的指导更加重要! - alcalde
显示剩余7条评论

5

如果您不需要原地操作字符串,而是生成其副本,请尝试使用以下代码:

 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]);

编辑:添加了#127用于删除控制字符。

编辑2:这是一个更快的版本,感谢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 ... 结构。 - Jeroen Wiert Pluimers
1
不用担心,你的解决方案将会正常工作。但对于非Ascii字符,需要使用CharInSet函数。 - Jeroen Wiert Pluimers
3
非常慢,它会一遍又一遍地重新分配结果。我会将结果设置为与原始字符串相同的长度,然后在处理完后设置实际长度。 - user160694

3
这是一个不逐个字符附加构建字符串的版本,而是一次性分配整个字符串。需要对字符串进行两次处理,一次计算“好”的字符数量,一次有效地复制这些字符,但它值得这样做,因为它不会进行多次重新分配:
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;

1
为什么会有人踩这个?虽然这并不重要,但我很好奇。 - Cosmin Prund
我本来不会使用 StringOfChar,而是使用 SetLength(),但这并不是贬低我的原因,尽管它需要两次遍历字符串。 - user160694
它确实需要两次遍历字符串,但它保证最佳分配。如果对许多字符串执行此操作,则最佳分配将比仅遍历字符串一次更重要。 - Cosmin Prund
@Cosmin 多次遍历的一个缺点是这段代码有两个相同的 if 语句,违反了 DRY 原则。 - David Heffernan
@David,没错。说实话,我更看重DRY原则,而不是运行时性能。我不编写速度关键的应用程序。 - Cosmin Prund
显示剩余2条评论

0

我的性能解决方案;

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编写的。


0

我的版本,带有结果字节数组:

接口

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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接