Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to concat multiple strings with Move?

How can I concat an array of strings with Move. I tried this but I just cannot figure how to get Move operation working correctly.

program Project2;

{$POINTERMATH ON}

procedure Concat(var S: String; const A: Array of String);
var
  I, J: Integer;
  Len: Integer;
begin
  Len := 0;
  for I := 0 to High(A) do
  Len := Len + Length(A[I]);

  SetLength(S, Length(S) + Len);

  for I := 0 to High(A) do
  Move(PWideChar(A[I])[0], S[High(S)], Length(A[I]) * SizeOf(WideChar));
end;

var
  S: String;
begin
  S := 'test';
  Concat(S, ['test', 'test2', 'test3']);
end.
like image 741
user15124 Avatar asked Nov 03 '15 01:11

user15124


People also ask

How can you concatenate multiple strings?

Concatenation is the process of appending one string to the end of another string. You concatenate strings by using the + operator. For string literals and string constants, concatenation occurs at compile time; no run-time concatenation occurs.

What is the most efficient way to concatenate many strings together?

If you are concatenating a list of strings, then the preferred way is to use join() as it accepts a list of strings and concatenates them and is most readable in this case. If you are looking for performance, append/join is marginally faster there if you are using extremely long strings.

Can Strcat take multiple arguments?

Concatenates between 1 and 64 arguments.

Is interpolation better than concatenation?

When you concatenate strings with a + sign, you end up creating multiple strings to get to the final string. When you use the interpolation method (or StringBuilder), the . NET runtime optimizes your string use, so it (in theory) has better memory usage.


2 Answers

In the second loop you forget that S already has the right size to get filled with all the elements so you have to use another variable to know the destination parameter of Move

procedure Concat(var S: String; const A: Array of String);
var
  I, Len, Sum: Integer;
begin
  Len := 0;
  for I := 0 to High(A) do
    Inc(Len, Length(A[I]));
  Sum := Length(S);
  SetLength(S, Sum + Len);
  for I := 0 to High(A) do
  begin
    if Length(A[I]) > 0 then
      Move(A[I][1], S[Sum+1], Length(A[I]) * SizeOf(Char));
    Inc(Sum, Length(A[I]));
  end;
end;  

Casting the source parameter to PWideChar is totally superfluous since the Move function use a kind of old generic syntax that allows to pass everything you want (const Parameter without type).

like image 45
Abstract type Avatar answered Sep 22 '22 13:09

Abstract type


I'd write this function like so:

procedure Concat(var Dest: string; const Source: array of string);
var
  i: Integer;
  OriginalDestLen: Integer;
  SourceLen: Integer;
  TotalSourceLen: Integer;
  DestPtr: PChar;
begin
  TotalSourceLen := 0;
  OriginalDestLen := Length(Dest);
  for i := low(Source) to high(Source) do begin
    inc(TotalSourceLen, Length(Source[i]));
  end;
  SetLength(Dest, OriginalDestLen + TotalSourceLen);

  DestPtr := PChar(Pointer(Dest)) + OriginalDestLen;
  for i := low(Source) to high(Source) do begin
    SourceLen := Length(Source[i]);
    Move(Pointer(Source[i])^, DestPtr^, SourceLen*SizeOf(Char));
    inc(DestPtr, SourceLen);
  end;
end;

It's fairly self-explanatory. The complications are caused by empty strings. Any attempt to index characters of an empty string will lead to exceptions when range checking is enabled.

To handle that complication, you can add if tests for the case where one of the strings involved in the Move call is empty. I prefer a different approach. I'd rather cast the string variable to be a pointer. That bypasses range checking but also allows the if statement to be omitted.

Move(Pointer(Source[i])^, DestPtr^, SourceLen*SizeOf(Char));

One might wonder what happens if Source[i] is empty. In that case Pointer(Source[i]) is nil and you might expect an access violation. In fact, there is no error because the length of the move as specified by the third argument is zero, and the nil pointer is never actually de-referenced.

The other line of note is here:

DestPtr := PChar(Pointer(Dest)) + OriginalDestLen;

We use PChar(Pointer(Dest)) rather than PChar(Dest). The latter invokes code to check whether or not Dest is empty, and if so yields a pointer to a single null-terminator. We want to avoid executing that code, and obtain the address held in Dest directly, even if it is nil.

like image 133
David Heffernan Avatar answered Sep 21 '22 13:09

David Heffernan