当最后一个值为空时,String.Split工作很奇怪

时间:2015-02-09 13:27:47

标签: delphi delphi-xe6

我想将我的字符串拆分为数组,但是当最后一个"值"是空的。请看我的例子。是bug还是功能?有没有办法在没有解决方法的情况下使用这个函数?

var
  arr: TArray<string>;

  arr:='a;b;c'.Split([';']); //length of array = 3, it's OK
  arr:='a;b;c;'.Split([';']); //length of array = 3, but I expect 4
  arr:='a;b;;c'.Split([';']); //length of array = 4 since empty value is inside
  arr:=('a;b;c;'+' ').Split([';']); //length of array = 4 (primitive workaround with space)

2 个答案:

答案 0 :(得分:7)

此行为无法更改。您无法自定义此拆分功能的工作方式。我怀疑你需要提供自己的拆分实现。 Michael Erikkson在评论中指出System.StrUtils.SplitString以你想要的方式表现出来。

在我看来,设计很差。例如

Length('a;'.Split([';'])) = 1

然而

Length(';a'.Split([';'])) = 2

这种不对称性表明设计不佳。令人惊讶的是,测试没有发现这一点。

设计如此明显可疑这一事实意味着可能值得提交错误报告。我预计它会被拒绝,因为任何更改都会影响现有代码。但你永远不知道。

我的建议:

  1. 使用您自己的分割实现,可以根据需要执行。
  2. 提交错误报告。

  3. 虽然System.StrUtils.SplitString做了你想要的,但它的表现并不好。这很可能无所谓。在这种情况下你应该使用它。但是,如果表现很重要,那么我提供这个:

    {$APPTYPE CONSOLE}
    
    uses
      System.SysUtils, System.Diagnostics, System.StrUtils;
    
    function MySplit(const s: string; Separator: char): TArray<string>;
    var
      i, ItemIndex: Integer;
      len: Integer;
      SeparatorCount: Integer;
      Start: Integer;
    begin
      len := Length(s);
      if len=0 then begin
        Result := nil;
        exit;
      end;
    
      SeparatorCount := 0;
      for i := 1 to len do begin
        if s[i]=Separator then begin
          inc(SeparatorCount);
        end;
      end;
    
      SetLength(Result, SeparatorCount+1);
      ItemIndex := 0;
      Start := 1;
      for i := 1 to len do begin
        if s[i]=Separator then begin
          Result[ItemIndex] := Copy(s, Start, i-Start);
          inc(ItemIndex);
          Start := i+1;
        end;
      end;
      Result[ItemIndex] := Copy(s, Start, len-Start+1);
    end;
    
    const
      InputString = 'asdkjhasd,we1324,wqweqw,qweqlkjh,asdqwe,qweqwe,asdasdqw';
    
    var
      i: Integer;
      Stopwatch: TStopwatch;
    
    const
      Count = 3000000;
    
    begin
      Stopwatch := TStopwatch.StartNew;
      for i := 1 to Count do begin
        InputString.Split([',']);
      end;
      Writeln('string.Split: ', Stopwatch.ElapsedMilliseconds);
    
      Stopwatch := TStopwatch.StartNew;
      for i := 1 to Count do begin
        System.StrUtils.SplitString(InputString, ',');
      end;
      Writeln('StrUtils.SplitString: ', Stopwatch.ElapsedMilliseconds);
    
      Stopwatch := TStopwatch.StartNew;
      for i := 1 to Count do begin
        MySplit(InputString, ',');
      end;
      Writeln('MySplit: ', Stopwatch.ElapsedMilliseconds);
    end.
    

    在我的E5530上使用XE7构建的32位版本的输出是:

    string.Split: 2798
    StrUtils.SplitString: 7167
    MySplit: 1428
    

答案 1 :(得分:2)

以下与接受的答案非常相似,但i)它是一个辅助方法,ii)它接受一个分隔符数组。

由于这些原因,该方法比大卫的时间长约30%,但无论如何都可能有用。

program ImprovedSplit;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  TStringHelperEx = record helper for string
  public
    function SplitEx(const Separator: array of Char): TArray<string>;
  end;

var
  TestString : string;
  StringArray : TArray<String>;


{ TStringHelperEx }

function TStringHelperEx.SplitEx( const Separator: array of Char ): TArray<string>;
var
  Str : string;
  Buf, Token : PChar;
  i, cnt : integer;
  sep : Char;
begin
  cnt := 0;
  Str := Self;
  Buf := @Str[1];
  SetLength(Result, 0);

  if Assigned(Buf) then begin

    for sep in Separator do begin
      for i := 0 to Length(Self) do begin
        if Buf[i] = sep then begin
          Buf[i] := #0;
          inc(cnt);
        end;
      end;
    end;

    SetLength(Result, cnt + 1);

    Token := Buf;
    for i := 0 to cnt do begin
      Result[i] := StrPas(Token);
      Token := Token + Length(Token) + 1;
    end;

  end;
end;

begin
  try
    TestString := '';
    StringArray := TestString.SplitEx([';']);
    Assert(Length(StringArray) = 0, 'Failed test for Empty String');

    TestString := 'a';
    StringArray := TestString.SplitEx([';']);
    Assert(Length(StringArray) = 1, 'Failed test for Single String');

    TestString := ';';
    StringArray := TestString.SplitEx([';']);
    Assert(Length(StringArray) = 2, 'Failed test for Single Separator');

    TestString := 'a;';
    StringArray := TestString.SplitEx([';']);
    Assert(Length(StringArray) = 2, 'Failed test for Single String + Single End-Separator');

    TestString := ';a';
    StringArray := TestString.SplitEx([';']);
    Assert(Length(StringArray) = 2, 'Failed test for Single String + Single Start-Separator');

    TestString := 'a;b;c';
    StringArray := TestString.SplitEx([';']);
    Assert(Length(StringArray) = 3, 'Failed test for Simple Case');

    TestString := ';a;b;c;';
    StringArray := TestString.SplitEx([';']);
    Assert(Length(StringArray) = 5, 'Failed test for Start and End Separator');

    TestString := '0;1;2;3;4;5;6;7;8;9;0;1;2;3;4;5;6;7;8;9;0;1;2;3;4;5;6;7;8;9;0;1;2;3;4;5;6;7;8;9';
    StringArray := TestString.SplitEx([';', ',']);
    Assert(Length(StringArray) = 40, 'Failed test for Larger Array');

    TestString := '0;1;2;3;4;5;6;7;8;9;0;1;2;3;4;5;6;7;8;9;0,1,2,3,4,5,6,7,8,9,0;1;2;3;4;5;6;7;8;9';
    StringArray := TestString.SplitEx([';', ',']);
    Assert(Length(StringArray) = 40, 'Failed test for Array of Separators');

    Writeln('No Errors');

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

  Writeln('Press ENTER to continue');
  Readln(TestString);

end.