String.Split 最后一个值为空时工作异常
String.Split works strange when last value is empty
我想将我的字符串拆分为数组,但当最后一个 "value" 为空时效果不佳。请看我的例子。是错误还是功能?有什么方法可以在没有解决方法的情况下使用这个功能吗?
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)
无法更改此行为。您无法自定义此拆分功能的工作方式。我怀疑您需要提供自己的拆分实现。迈克尔·埃里克森 (Michael Erikkson) 在评论中很有帮助地指出 System.StrUtils.SplitString
以您想要的方式行事。
在我看来设计很差。例如
Length('a;'.Split([';'])) = 1
还有
Length(';a'.Split([';'])) = 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.
在我的 E5530 上使用 XE7 构建的 32 位版本的输出是:
string.Split: 2798
StrUtils.SplitString: 7167
MySplit: 1428
以下与已接受的答案非常相似,但 i) 它是一种辅助方法,并且 ii) 它接受一组分隔符。
由于这些原因,该方法比 David 的方法花费的时间大约长 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.
我想将我的字符串拆分为数组,但当最后一个 "value" 为空时效果不佳。请看我的例子。是错误还是功能?有什么方法可以在没有解决方法的情况下使用这个功能吗?
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)
无法更改此行为。您无法自定义此拆分功能的工作方式。我怀疑您需要提供自己的拆分实现。迈克尔·埃里克森 (Michael Erikkson) 在评论中很有帮助地指出 System.StrUtils.SplitString
以您想要的方式行事。
在我看来设计很差。例如
Length('a;'.Split([';'])) = 1
还有
Length(';a'.Split([';'])) = 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.
在我的 E5530 上使用 XE7 构建的 32 位版本的输出是:
string.Split: 2798 StrUtils.SplitString: 7167 MySplit: 1428
以下与已接受的答案非常相似,但 i) 它是一种辅助方法,并且 ii) 它接受一组分隔符。
由于这些原因,该方法比 David 的方法花费的时间大约长 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.