2015-02-09 2 views
9

Я хотел бы разделить мою строку на массив, но он плохо работает, когда последнее «значение» пусто. См. Мой пример, пожалуйста. Это ошибка или функция? Есть ли способ использовать эту функцию без обходных решений?String.Split работает странно, когда последнее значение пусто.

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

Именно так оно и было спроектировано. Если вам это не нравится, напишите свою собственную функцию разделения. –

+0

Хорошо, спасибо Дэвиду. –

+0

Что происходит с '; x'? Вы получаете одно значение или два? Если вы получаете два, тогда дизайн несимметричен, это плохо. –

ответ

7

Данное поведение не может быть изменено. Невозможно настроить, как эта функция разделения работает. Я подозреваю, что вам нужно будет предоставить свою собственную раздельную реализацию. Майкл Эрикссон в своем замечании замечает, что System.StrUtils.SplitString ведет себя так, как вам хочется.

Дизайн кажется мне бедным. К примеру

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

и еще

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

Эта асимметрия является четким признаком плохого дизайна. Удивительно, что тестирование не выявило этого.

Тот факт, что дизайн настолько четко подозревается, означает, что, возможно, стоит представить отчет об ошибке. Я ожидаю, что это будет отклонено, поскольку любое изменение повлияет на существующий код. Но ты никогда не знаешь.

Мои рекомендации:

  1. Используйте собственную реализацию разделения, который выполняет, как вам требуется.
  2. Отправить отчет об ошибке.

Хотя 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. 

Выход 32 разрядного выпуска сборки с XE7 на моем E5530 является:

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

Такое поведение в основном основано на слишком большом понимании того, что должен иметь программист, вместо того, чтобы строить его по фиксированному логическому правилу: «Сепаратор разделяет два значения», и теперь нужно, чтобы массив результатов содержал счетчик разделителей плюс один значения. –

+0

@SirRufo Хотя я полностью согласен с настроением, я не думаю, что поведение преднамеренно. В D5 'TStrings.CommaText' имеет аналогичную проблему. Когда я посмотрел на код, это была простая ошибка: если символ ',' появился как последний символ входной строки, он бы: прочитал символ, так что следующий Char будет # 0 (терминатор для PChar) и начнет следующую итерацию петля. Но 'NextChar = # 0' было завершающим условием для цикла, так что это закончило бы цикл. К D2007 это было исправлено с дополнительным кодом, чтобы явно добавить пустую строку в этом случае. –

+0

@CraigYoung Это действительно ошибка, но я говорил о том, как в основном это вызвано ошибками. Кто-то реализует его, и я надеюсь, что проверит его (так же, как и я). Но я думаю, что нет UnitTest и/или нет описания всего поведения. Иногда я думаю, что UnitTest просто выполняется: «Он компилируется!» - Это заставляет меня строить некоторые (не слишком много, но растущие) UnitTest для RTL. –

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. 
Смежные вопросы