2015-02-09 8 views
9

मैं अपनी स्ट्रिंग को सरणी में विभाजित करना चाहता हूं लेकिन आखिरी "मान" खाली होने पर यह खराब काम करता है। कृपया मेरा उदाहरण देखें। क्या यह बग या फीचर है? क्या वर्कअराउंड के बिना इस फ़ंक्शन का उपयोग करने का कोई तरीका है?स्ट्रिंग। स्प्लिट अजीब काम करता है जब अंतिम मान खाली होता है

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 बिट रिहाई के उत्पादन में मेरी E5530 पर XE7 साथ निर्माण होता है:

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

यह व्यवहार अधिकतर सोचने पर आधारित है कि प्रोग्रामर को दिमाग में क्या होना चाहिए, इसे एक निश्चित लॉजिकल नियम पर बनाने के बजाय: "विभाजक दो मानों को अलग करता है" और अब किसी को भी चाहिए, कि परिणाम सरणी में अलग-अलग गिनती होगी और एक मान। –

+0

@ सररूफो, जबकि मैं भावनाओं से पूरी तरह से सहमत हूं, मुझे नहीं लगता कि व्यवहार जानबूझकर है। D5 'TStrings.CommaText' में एक समान समस्या है। जब मैंने कोड को देखा तो यह एक साधारण बग था: यदि '', 'अंतिम स्ट्रिंग इनपुट स्ट्रिंग के रूप में दिखाई देता है, तो यह होगा: चरित्र को पढ़ें, इसलिए अगला चार # 0 (पीसीहर के लिए टर्मिनेटर) है और इसके अगले पुनरावृत्ति प्रारंभ करें पाश। लेकिन 'NextChar = # 0' लूप के लिए समाप्ति की स्थिति थी, जिससे लूप समाप्त हो जाएगा। डी 2007 द्वारा इस मामले में स्पष्ट रूप से खाली स्ट्रिंग जोड़ने के लिए अतिरिक्त कोड के साथ तय किया गया था। –

+0

@CraigYoung यह वास्तव में एक बग है, लेकिन मैं इस बारे में बात कर रहा था कि इस छोटी गाड़ी के कार्यान्वयन के कारण ज्यादातर कारण होते हैं। कोई इसे लागू करता है और मुझे उम्मीद है कि यह परीक्षण करेगा (इसलिए मैं करता हूं)। लेकिन मुझे लगता है कि कोई यूनिटटेस्ट नहीं है और/या पूरे व्यवहार का कोई विवरण नहीं है। कभी-कभी मुझे लगता है कि यूनिटटेस्ट बस द्वारा किया जाता है: "यह संकलित करता है!" - इससे मुझे आरटीएल के लिए यूनिटटेस्ट कुछ (अभी तक बहुत ज्यादा नहीं बढ़ रहा है) बनाने के लिए प्रेरित करता है। –

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. 
संबंधित मुद्दे