2015-02-09 21 views
9

Chciałbym podzielić mój ciąg do tablicy, ale działa źle, gdy ostatnia "wartość" jest pusta. Zobacz mój przykład, proszę. Czy to błąd lub funkcja? Czy istnieje sposób korzystania z tej funkcji bez obejścia?String.Split działa dziwnie, gdy ostatnia wartość jest pusta

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

Tak właśnie zaprojektowano. Jeśli ci się nie podoba, napisz własną funkcję podziału. –

+0

OK, dziękuję David. –

+0

Co dzieje się z "; x"? Czy otrzymujesz jedną wartość lub dwie? Jeśli dostaniesz dwa, to projekt jest asymetryczny, coś złego. –

Odpowiedz

7

Tego zachowania nie można zmienić. Nie ma sposobu, aby dostosować sposób działania tej funkcji podziału. Podejrzewam, że musisz podać własną implementację podziału. Michael Erikkson pomocnie wskazuje w komentarzu, że System.StrUtils.SplitString zachowuje się w sposób, w jaki pragniesz.

Wygląda na to, że projekt jest kiepski. Na przykład

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

i jeszcze

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

Ta asymetria jest wyraźne wskazanie złej konstrukcji. To zadziwiające, że testy tego nie zidentyfikowały.

Fakt, że projekt jest tak wyraźnie podejrzany, oznacza, że ​​warto złożyć zgłoszenie błędu. Oczekuję, że odmówi, ponieważ każda zmiana wpłynie na istniejący kod. Ale nigdy nie wiesz.

Moje zalecenia:

  1. Użyj własnego wdrożenie podziału, który wykonuje, jak potrzebujesz.
  2. Zgłoś zgłoszenie błędu.

Podczas System.StrUtils.SplitString robi to, co chcesz, jego wydajność nie jest wielki. To bardzo prawdopodobne, nie ma znaczenia. W takim przypadku powinieneś go użyć. Jednakże, jeśli kwestiach wydajności, a potem zaoferować to:

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

Wyjście uwolnienie 32 bit budować z XE7 na moim E5530 jest:

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

To zachowanie jest w większości oparte na zbytnim zastanawianiu się, co powinno mieć na myśli programista, zamiast budowania go na ustalonej logicznej zasadzie: "Separator rozdziela dwie wartości" i teraz każdy powinien teraz, aby tablica wyników zawierała liczbę separatorów plus jeden wartości. –

+0

@SirRufo Chociaż zgadzam się z sentymentem całym sercem, nie sądzę, że zachowanie jest zamierzone. W D5 'TStrings.CommaText' ma podobny problem. Kiedy spojrzałem na kod, był to zwykły błąd: jeśli ',' pojawi się jako ostatni znak wejściowy, to: odczyta znak, więc następny znak Char będzie # 0 (terminator dla PChar) i rozpocznie następną iterację pętla. Ale 'NextChar = # 0' był warunkiem zakończenia pętli, więc zakończyłoby to pętlę. W D2007 zostało to naprawione dodatkowym kodem, aby wyraźnie dodać pusty ciąg w tym przypadku. –

+0

@CraigYoung Jest to rzeczywiście błąd, ale mówiłem o tym, jak te błędne implementacje są w większości spowodowane przez. Ktoś ją zaimplementował i mam nadzieję, że ją przetestuje (ja też). Ale myślę, że nie ma UnitTest i/lub brak opisu całego zachowania. Czasami myślę, że UnitTest jest po prostu robiony przez: "Kompiluje!" - To prowadzi mnie do zbudowania (niezbyt wielu, ale rosnących) UnitTest dla RTL. –

2

Poniżej jest bardzo podobny do przyjętego odpowiedź ale i) jest to metoda pomocnicza i ii) akceptuje tablicę separatorów.

Z tych powodów metoda zajmuje o 30% więcej czasu niż David's, ale i tak może być przydatna.

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.