2015-02-09 9 views
9

Ich möchte meine Zeichenfolge in Array teilen, aber es funktioniert schlecht, wenn letzte "Wert" leer ist. Siehe mein Beispiel bitte. Ist es ein Fehler oder Feature? Gibt es eine Möglichkeit, diese Funktion ohne Problemumgehungen zu verwenden?String.Split funktioniert seltsam, wenn der letzte Wert leer ist

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) 
+0

So wurde es entworfen. Wenn Sie es nicht mögen, schreiben Sie Ihre eigene Split-Funktion. –

+0

OK, danke David. –

+0

Was passiert mit '; x'? Erhalten Sie einen Wert oder zwei? Wenn Sie zwei bekommen, dann ist das Design asymmetrisch, eine schlechte Sache. –

Antwort

7

Dieses Verhalten kann nicht geändert werden. Sie können die Funktionsweise dieser Split-Funktion nicht anpassen. Ich vermute, dass Sie Ihre eigene Split-Implementierung bereitstellen müssen. Michael Erikkson weist in einem Kommentar darauf hin, dass sich System.StrUtils.SplitString in der von Ihnen gewünschten Weise verhält.

Das Design scheint mir arm zu sein. Zum Beispiel

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

und noch

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

Diese Asymmetrie ist ein deutliches Zeichen für schlechtes Design. Es ist erstaunlich, dass Tests dies nicht identifizierten.

Die Tatsache, dass das Design so eindeutig verdächtig ist, bedeutet, dass es sich lohnt, einen Fehlerbericht einzureichen. Ich würde erwarten, dass es abgelehnt wird, da jede Änderung den bestehenden Code beeinflussen würde. Aber du weißt nie.

Meine Empfehlungen:

  1. Ihre eigene geteilte Implementierung verwenden, die, wie Sie führt erfordern.
  2. Reichen Sie einen Fehlerbericht ein.

Während System.StrUtils.SplitString tut, was Sie wollen, seine Leistung nicht groß ist. Das ist sehr wahrscheinlich egal. In diesem Fall sollten Sie es verwenden. Wenn jedoch Leistung zählt, dann biete ich dies:

{$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. 

Der Ausgang eines 32 Bit-Version mit XE7 auf meinem E5530 bauen ist:

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

Dieses Verhalten basiert meist auf zu viel Denken, was den Programmierer im Auge haben sollte, anstatt es auf eine feste logische Regel aufzubauen: "Das Trennzeichen trennt zwei Werte" und jetzt sollte jeder, dass das Ergebnis-Array separater count plus eins enthalten soll Werte. –

+0

@SirRufo Obwohl ich von ganzem Herzen der Stimmung zustimme, glaube ich nicht, dass das Verhalten beabsichtigt ist. In D5 hat 'TStrings.CommaText' ein ähnliches Problem. Als ich den Code anschaute, war es ein einfacher Fehler: Wenn ein ',' als letztes Zeichen der Eingabezeichenfolge angezeigt wurde, würde es: das Zeichen lesen, also ist Char # 0 (Terminator für PChar) und startet die nächste Iteration der Schleife. Aber 'NextChar = # 0 'war die Endbedingung für die Schleife, also würde die Schleife enden. Bis D2007 wurde dies mit extra Code behoben, um in diesem Fall explizit eine leere Zeichenfolge hinzuzufügen. –

+0

@CraigYoung Es ist in der Tat ein Fehler, aber ich habe darüber gesprochen, wie diese Buggy-Implementierungen hauptsächlich von verursacht werden. Jemand implementiert es und ich hoffe, es wird getestet (ich auch). Aber ich denke, es gibt keinen UnitTest und/oder keine Beschreibung des gesamten Verhaltens. Manchmal denke ich, dass der UnitTest einfach gemacht wird durch: "Es kompiliert!" - Das bringt mich dazu, einige (nicht zu viele, aber wachsende) UnitTests für die RTL zu bauen. –

2

Das folgende ist sehr ähnlich wie die akzeptierte Antwort aber i) es ist eine Hilfsmethode und ii) es akzeptiert ein Array von Separatoren.

Die Methode dauert aus diesen Gründen etwa 30% länger als die von David, kann aber trotzdem nützlich sein.

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.