Можно ли добавить 2 массива в один?

Есть ли простой простой способ добавить 2 массива в один? В приведенном ниже примере нельзя просто использовать инструкцию C := A + B... Я хотел бы избегать делать это каждый раз.

TPerson = record
    Birthday: Tdate;
    Name, Surname:string;
end;

Tpeople = array of TPerson;

var A, B, C:Tpeople;

C:=A+B; // it is not possible

спасибо

Ответ 1

Из-за двух полей string в каждой записи TPerson вы не можете просто использовать двоичный "ход", так как вы будете использовать подсчет ссылок string - особенно в многопоточной среде.

Вы можете сделать это вручную - это быстро и приятно:

TPerson = record
  Birthday: TDate;
  Name, Surname: string;
end;

TPeople = array of TPerson;

var A, B, C: TPeople;

// do C:=A+B
procedure Sum(const A,B: TPeople; var C: TPeople);
begin
var i, nA,nB: integer;
begin
  nA := length(A);
  nB := length(B);
  SetLength(C,nA+nB);
  for i := 0 to nA-1 do
    C[i] := A[i];
  for i := 0 to nB-1 do
    C[i+nA] := B[i];
end;

Или вы можете использовать нашу обертку TDynArray, которая имеет метод обработки таких случаев:

procedure AddToArray(var A: TPeople; const B: TPeople);
var DA: TDynArray;
begin
  DA.Init(TypeInfo(TPeople),A);
  DA.AddArray(B); // A := A+B
end;

Метод AddArray может добавить подпорт исходного массива:

/// add elements from a given dynamic array
// - the supplied source DynArray MUST be of the same exact type as the
// current used for this TDynArray
// - you can specify the start index and the number of items to take from
// the source dynamic array (leave as -1 to add till the end)
procedure AddArray(const DynArray; aStartIndex: integer=0; aCount: integer=-1);

Обратите внимание, что с такими записями он будет использовать функцию RTL System._CopyRecord, которая не оптимизирована для скорости. Я написал более быструю версию - см. эту статью в блоге или эта тема форума.

Если вы используете динамические массивы в функциях/процедурах, не забудьте явно использовать параметры const или var (как указано выше), иначе он будет делать временную копию при каждом вызове, поэтому может быть медленно.

Ответ 2

В нем нет ничего, что позволяет объединить динамические массивы.

Вы можете использовать один из общих классов контейнеров, найденный в Generics.Collections, TList.

В вашем случае у вас будет 3 экземпляра TList, скажем A, B и C. Затем вы можете написать

A.Clear;
A.AddRange(B);
A.AddRange(C);

Я думаю, что это так близко, что вы можете получить то, что хотите, из того, что доставлено из коробки.

Если вы готовы сделать немного кодирования самостоятельно, вы можете использовать перегрузку оператора, чтобы использовать точный синтаксис, который вам нужен. Объявите запись, содержащую массив TPerson с частной видимостью. Затем вам необходимо реализовать оператор Add, свойство Count и свойство Items [] по умолчанию. Это можно сделать общим, так что вам нужно только написать его один раз.

TTurboArray = record<T>
private
  FItems: array of T;
  //property accessors here
 public
   class operator Add(a, b: TTurboArray<T>): TTurboArray<T>;
   property Count: Integer read GetCount write SetCount;
   property Items[Index: Integer]: T read GetItem write SetItem; default;
end;

Эта идея может быть расширена до очень мощной структуры данных, как вы сочтете нужным.

Ответ 3

Существует быстрый и грязный способ сделать это. Это ужасный взлом, но он должен работать и даже следить за подсчетом ссылок:

function ConcatPeople(const A, B: TPeople): TPeople;
var
  Temp: TPeople;
  ALen, BLen: Integer;
begin
  Result := Copy(A);
  BLen := Length(B);
  if BLen = 0 then
    Exit;
  ALen := Length(A);
  Temp := Copy(B);
  SetLength(Result, ALen + BLen);
  Move(Temp[0], Result[ALen], BLen * SizeOf(B[0]));
  FillChar(Temp[0], BLen * SizeOf(B[0]), 0);
end;

Фактически, данные в Temp "заменяются" пустыми записями в Result, поэтому баланс сохраняется, и пересчет будет продолжать работать.

Update

Для чего это стоит: это, по-видимому, тот же метод, что и в этот принятый SO-ответ и, например, TList < Т > .Вставьте. Я удалил этот ответ, но я все еще думаю, что он действителен, поэтому я снова его восстановил. Это может сделать с блокировкой вокруг блока Move/FillChar, поэтому никто не обращается к элементам, когда их "обменивают". Я добавлю это.

Ответ 4

Вот как я справился с этим, хотя для его использования был необходим небольшой (но, надеюсь, нематериальный для вас) вариант, чтобы использовать TArray:

(проверено в XE2)

uses
  Generics.Collections;

type
  TArrayExt = class(TArray)
    class function Concat<T>(const First, Second: array of T): TArray<T>; overload;
  end;

class function TArrayExt.Concat<T>(const First, Second: array of T): TArray<T>;
var
  i: Integer;
  l: Integer;
begin
  l := Length(First);
  SetLength(Result, l + Length(Second));
  for i := Low(First) to High(First) do
    Result[i] := First[i];
  for i := Low(Second) to High(Second) do
    Result[l + i] := Second[i];
end;

type
  TPerson = record
    Birthday: TDate;
    Name, Surname: String;
  end;

  TPeople = TArray<TPerson>;

var
  A, B, C: TPeople;
begin
  C := TArrayExt.Concat<TPerson>(A, B);

Основное отличие здесь в том, что я использую "TArray", а не "массив TPerson". Это можно использовать для строк массивов, записей и т.д. Я считаю, что основное преимущество этого сделать заключается в том, что он действительно делает копию, а не движение. И я использую "нормальные" функции Delphi вместо таких вещей, как объемные копии памяти, которые могут дать мне завещания.

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

(Если кто-то не публикует комментарий о том, как здесь происходит какая-то ужасная скрытая утечка памяти. Надеюсь, что нет!)