Question

procedure Split(S: String; List: TStringList; Separator: Char);
var
  P, C: PAnsiChar;
  S, Buff: String;
begin
  List.Clear;

  if S = '' then
    Exit;

  List.BeginUpdate;

  (* [Ajusting size  - Slow *)
  if S[1] = Separator then
    Insert('', S, 1);

  S := S + Separator;
  (* Adjusting size] *)

  //Get Pointer to data
  P := PChar(S);

  //initial position
  C := P;
  while P^ <> #0 do //check if reached the end of the string
  begin
    //when found a separator
    if P^ = Separator then
    begin
      if P = C then //check if the slot is empty
        Buff := ''
      else //when it is not empty, make an string buffer
        SetString(Buff, C, P-C);

      List.Add(Buff); //add the string into the list
      Inc(C, P-C+1); //moves the pointer C to the adress of the pointer P
    end;

    Inc(P); //go to next char in the string
  end;

  List.EndUpdate;
end;

This code is working fine but is moving the string 3 times in the memory:

In the Method Call (By copy)
In the Insert('', S, 1)
In the Concatenation: S := S + Separator;

I thought about adding const keyword in the S parameter, creating an internal string to copy the data more or less like this:

  if S[1] = Separator then
  begin
    SetLength(Str, Length(S)+2);
    //HERE!! how to copy the string  
    Str[1] := ' ';
  end
  else
  begin
    SetLength(Str, Length(S)+1);
    //HERE!! how to copy the string   
  end;

  //Add Separator in the last position
  Str[Length(Str)] := Separator;

Thus:

if the S contains ';'
it will create an stringlist with 2 items ('','').
if the S contains ';A'
it will create an stringlist with 2 items ('','A').
if the S contains 'A;A'
it will create an stringlist with 2 items ('A','A').
if the S contains 'A;'
it will create an stringlist with 2 items ('A','').

Était-ce utile?

La solution

Like this:

if S[1] = Separator then
begin
  SetLength(Str, Length(S)+2);
  Move(Pointer(S)^, Str[2], Length(S)*SizeOf(Char));
  S[1] := ' '; // surely you mean Str[1] := ' '
end
else
begin
  SetLength(Str, Length(S)+1);
  Move(Pointer(S)^, Str[1], Length(S)*SizeOf(Char));
end;

//Add Separator in the last position
Str[Length(Str)] := Separator;

It would be easy enough to re-work this to avoid the duplication.

var
  dest: PChar;

if S[1] = Separator then
begin
  SetLength(Str, Length(S)+2);
  dest := @Str[2];
  S[1] := ' '; // surely you mean Str[1] := ' '
end
else
begin
  SetLength(Str, Length(S)+1);
  dest := @Str[1];
end;
Move(Pointer(S)^, dest^, Length(S)*SizeOf(Char));

//Add Separator in the last position
Str[Length(Str)] := Separator;

And so on. I'll leave it to you to polish it up.

Autres conseils

The following routine is one I wrote (more accurately, adapted from SetDelimitedText and ExtractStrings) for Delphi 7 to handle the lack of the TStrings.StrictDelimiter property. Given the correct parameters, it'll return exactly the results you want.

{
SplitString will expand the delimited string S into its component parts and
store them in Strings.  The primary difference between this routine and
Classes.ExtractStrings and TStrings.DelimitedText is that it does not treat
spaces, tabs, and CR/LF as delimiters whether you like it or not.  If Quotes
is non-empty, then quoted strings will be handled correctly.

Leading and Trailing whitespace is significant if TrimStrings is False.

If you want to eliminate empty tokens, set SkipEmptyStrings to True.

If you want Strings to be cleared before parsing, set ClearStrings to True.

This procedure is especially useful for dealing with CSV files exported from
Excel, since Excel does not quote a string unless it contains a comma.
Using ExtractStrings or TStrings.CommaText will fail with such files.

In Delphi 2006+, TStrings has the StrictDelimiter property that renders this
routine largely useless.
}
procedure SplitString(const S: string; Separators, Quotes: TSysCharSet; const Strings: TStrings; ClearStrings, TrimStrings, SkipEmptyStrings: Boolean);
var
  Head, Tail: PChar;
  Item: string;
  StringExists: Boolean;

  {$IF NOT Declared(CharInSet)}
  function CharInSet(C: Char; const CharSet: TSysCharSet): Boolean;
  begin
    Result := C in CharSet;
  end;
  {$IFEND}

begin
  StringExists := False;
  Strings.BeginUpdate;
  try
    if ClearStrings then
      Strings.Clear;
    if S = '' then
      Exit;

    Tail := PChar(S);
    while Tail^ <> #0 do begin
      if CharInSet(Tail^, Quotes) then
        Item := AnsiExtractQuotedStr(Tail, Tail^)
      else begin
        // Mark beginning of token
        Head := Tail;
        // Look for end of token, delineated by end of string or separator
        while (Tail^ <> #0) and not CharInSet(Tail^, Separators) do
          Inc(Tail);
        SetString(Item, Head, Tail - Head);
        if TrimStrings then begin
          Item := Trim(Item);
          Head := PChar(Item);
          if CharInSet(Head^, Quotes) then
            Item := Trim(AnsiExtractQuotedStr(Head, Head^));
        end;
        if not (SkipEmptyStrings and (Item = '')) then
          Strings.Append(Item);
      end;
      // If the last character in a string is a separator, then we need to mark
      // that another string exists, otherwise the next Inc(Tail) call will
      // place Tail^ at #0, we'll exit the while loop, and never know that there
      // was an empty string there to add.
      // --AAF
      StringExists := Tail^ <> #0;
      // Skip Separator
      if StringExists then
        Inc(Tail);
    end;
    // This can only happen if the very last character is a separator
    if StringExists and not SkipEmptyStrings then
      Strings.Append('');
  finally
    Strings.EndUpdate;
  end;
end;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top