Есть ли способ добавить 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

спасибо


person lyborko    schedule 18.08.2011    source источник
comment
это было рассмотрено здесь: stackoverflow.com/questions/6197290/   -  person Ram Kumar    schedule 18.08.2011
comment
@Rahul Приведенный в кавычках ответ отличается, поскольку он был посвящен массиву строк, и в данном случае это массив записей.   -  person Arnaud Bouchez    schedule 18.08.2011
comment
@Arnaud: похоже, что ответ использует ту же технику, что и в моем удаленном ответе. ‹g› При использовании этого метода не имеет значения, перемещаете ли вы отдельные строки или записи с управляемыми типами, поскольку счетчики ссылок не меняются или должны изменяться. И это, вероятно, так же небезопасно для потоков, если вы не можете сделать его атомарным.   -  person Rudy Velthuis    schedule 18.08.2011
comment
@Rudy Почему ты удалил свой ответ? Ваш было легче читать/понимать, чем тот, который цитируется в этом ТАК вопросе. Всегда есть несколько способов реализации, и если знать слабые/сильные стороны каждого из них, то всегда лучше цитировать каждый из них, ИМХО.   -  person Arnaud Bouchez    schedule 18.08.2011
comment
@Arnaud: да, действительно. По пути из дома в свою клинику (здесь) я уже решил его восстановить. TList‹T›.Insert также использует ту же технику.   -  person Rudy Velthuis    schedule 18.08.2011
comment
@Arnaud, Руди и Дэвид, что, если бы TPerson содержал некоторые динамические массивы строк или динамические массивы других записей? Будут ли предложенные вами решения обрабатывать глубокое копирование?   -  person LU RD    schedule 18.08.2011
comment
@LU Для решений цикла или TDynArray (т.е. двух решений в моем ответе) оба будут обрабатывать вложенные записи, вложенные динамические массивы и/или вложенные записи.   -  person Arnaud Bouchez    schedule 18.08.2011
comment
@LU ни одно из решений здесь не является глубокой копией. На самом деле, я думаю, что все решения здесь семантически эквивалентны.   -  person David Heffernan    schedule 18.08.2011


Ответы (5)


Из-за двух полей 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);

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

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

person Arnaud Bouchez    schedule 18.08.2011
comment
В случае dynarray временная копия не будет сделана. Увеличится только счетчик ссылок. Не так уж и важно. - person Rudy Velthuis; 18.08.2011
comment
@Rudy Конечно, ты прав насчет счетчика ссылок. Это просто общая схема хорошей практики. - person Arnaud Bouchez; 18.08.2011
comment
@ Хорошо, я согласен с хорошей практикой. Обратите внимание, что я избегаю использования const для интерфейсов, но здесь это не по теме. - person Rudy Velthuis; 18.08.2011
comment
FWIW, вместо использования цикла для копирования первого массива вы можете сделать: C := Copy(A); SetLength(C, nA + nB); do the second loop. Не уверен, что это быстрее, но выглядит быстрее. <грамм> - person Rudy Velthuis; 18.08.2011
comment
@Rudy ИМХО быстрее не будет. Фактически, System._CopyRecord будет вызываться для каждой записи внутри System._CopyArray низкоуровневой RTL-процедуры. Это именно то, что делает ручной цикл ... И один единственный SetLength позволит избежать перераспределения памяти (не имеет большого значения, но стоит упомянуть). - person Arnaud Bouchez; 18.08.2011

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

Вы можете рассмотреть возможность использования одного из универсальных классов контейнеров, найденных в 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;

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

person David Heffernan    schedule 18.08.2011
comment
Одна проблема с TTurboArray‹T› заключается в том, что, хотя первое значение выглядит как тип значения, если вы выполняете myTurboArray := yourTurboArray;, элементы в FItems не копируются, поэтому, если вы изменяете массив в yourTurboArray, myTurboArray тоже изменяется . Другими словами, никаких копий по требованию. - person Rudy Velthuis; 18.08.2011
comment
@rudy, другими словами, его семантика идентична динамическому массиву, что вряд ли является проблемой. Возможность добавления методов упрощает добавление функции для создания копии. - person David Heffernan; 18.08.2011
comment
Его семантика относится к ссылочному типу, но он является типом значения. Эта двойственность может быть проблемой. - person Rudy Velthuis; 18.08.2011
comment
@rudy Хватит копать себе яму. Запись, содержащая массив, практически неотличима от массива. Здесь нет проблем, которых нет и у динамического массива. - person David Heffernan; 18.08.2011
comment
Какая дыра? Есть проблема с двойственностью таких конструкций. Это не я придумал. - person Rudy Velthuis; 18.08.2011
comment
@rudy Вы не продемонстрировали, почему предлагаемая мной конструкция отличается от динамических массивов. Пока вы этого не сделаете, ваши комментарии бесполезны и вводят в заблуждение. Сейчас мы входим в бесконечный цикл, поэтому я предлагаю дать читателям возможность составить собственное мнение. - person David Heffernan; 18.08.2011
comment
@david Даже если универсальное решение похоже на функцию и, безусловно, является предпочтительным в современном кодировании Delphi, оно не использует динамические массивы ... в чем и заключался вопрос, ИМХО. Так что, я думаю, Руди, мы с тобой единственные, кто может общаться на этой SO-странице. ;) - person Arnaud Bouchez; 18.08.2011
comment
@arnaud использует динамические массивы, просто обернутые для удобства синтаксиса. - person David Heffernan; 18.08.2011
comment
@david: как и существующая реализация TList‹T›. С тем же успехом его можно было бы назвать TArrayList‹T›. - person Rudy Velthuis; 18.08.2011
comment
@rudy не может выполнять перегрузку операций для типов классов с помощью собственного компилятора Delphi. - person David Heffernan; 18.08.2011
comment
@ Дэвид: я знаю. Вместо этого можно использовать функцию. Ничего страшного, имхо. - person Rudy Velthuis; 18.08.2011
comment
FWIW, я отозвал свое решение и дал вашему +1. Я все еще думаю, что это решение может быть полезным, но для этого должен быть блочный обмен потоками. Это должно бить цикл по длине, если есть достаточно предметов для перемещения. Также может использоваться для перемещения элементов, когда элементы удаляются или вставляются в конструкцию, подобную Tlist‹T›. - person Rudy Velthuis; 18.08.2011
comment
@David Было бы неплохо поместить Count как свойство FCount и позволить массиву расти по желанию ... Вызов SetLength() для каждого добавления намного медленнее. Что касается динамического массива, он использует динамические массивы внутри, но не будет напрямую добавлять два динамических массива. Можно сделать процедуру... Но стоит ли? - person Arnaud Bouchez; 18.08.2011
comment
@arnaud да, вам нужен отдельный счет от емкости. Два преимущества этой предлагаемой структуры заключаются в том, что вы можете использовать перегрузку операции + и вы можете автоматически создавать экземпляры новых экземпляров, без необходимости в конструкторе. - person David Heffernan; 18.08.2011
comment
Я думаю, что каждый доступ к Items[] будет использовать временную копию записи из-за GetItem. Например, в цикле по всем элементам (например, при поиске значения) это может быть намного медленнее, чем прямой FItems[] доступ. Если сделать его закрытым, это может замедлить работу... Вот что мне нравится в нашей оболочке TDynArray, это просто оболочка вокруг существующего динамического массива, а не другая структура хранения. - person Arnaud Bouchez; 18.08.2011
comment
@David Насчет отсутствия необходимости в конструкторе: вы правы, это хорошая функция record<T>. - person Arnaud Bouchez; 18.08.2011
comment
@Arnaud, вместо того, чтобы предоставлять доступ к Items [], вместо этого я обычно предоставляю указатель на элементы. Просто чтобы убедиться, что временных копий никогда не будет. - person LU RD; 18.08.2011
comment
@arnaud Хорошие моменты. Что вы выберете, зависит от того, где вы предпочитаете находиться в спектре удобства по сравнению с количеством операций. Вы можете сделать так, чтобы GetItem возвращал ^T, если хотите пойти на другой компромисс. Я ценю, что массивы dyn работают быстро, но код, который получается, часто кажется мне слишком низкоуровневым, по крайней мере, при повседневном использовании. Хорошо для низкоуровневого кода lib, но, на мой взгляд, грязно для повседневного использования. - person David Heffernan; 18.08.2011
comment
@David А с указателями вы можете потерять некоторые сильные способности к типам ... некоторые пользователи боятся делать ошибки с указателями. При прямом доступе к динамическому массиву FItems[] этого не произойдет. С нашей оболочкой TDynArray я действительно заново открыл для себя динамические массивы: доступен высокоуровневый доступ (включая хеширование, сортировку и многое другое) с некоторыми приятными функциями. Например, определение свойства динамического массива для нашего ORM сделало его сегментированием. -ready без необходимости TList/TCollection, которые ожидают инициализации и финализации. - person Arnaud Bouchez; 19.08.2011
comment
@Arnaud, поскольку я когда-либо пишу код только для использования в своей организации, мне не нужно беспокоиться о пользователях, которые боятся указателей, но я понимаю вашу точку зрения. Однажды я посмотрю на твой дин-массив! - person David Heffernan; 19.08.2011

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

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, поэтому баланс сохраняется, а подсчет ссылок продолжает работать.

Обновлять

Для чего это стоит: это, по-видимому, тот же метод, который используется в этом принят ответ SO и, например, TList‹T›.Вставить. Я удалил этот ответ, но я все еще думаю, что он действителен, поэтому я снова удалил его. Это может быть связано с блокировкой вокруг блока Move/FillChar, чтобы никто не мог получить доступ к элементам, когда они «заменяются местами». Я добавлю это.

person Rudy Velthuis    schedule 18.08.2011
comment
Здесь лучше использовать простой цикл for - person David Heffernan; 18.08.2011
comment
Я не согласен. Простой цикл for намного медленнее и не безопаснее. Так почему же лучше? - person Rudy Velthuis; 18.08.2011
comment
Это безопаснее, потому что остается в рамках системы типов и не зависит от деталей реализации, которые могут быть изменены. - person David Heffernan; 18.08.2011
comment
ИМХО, эта реализация не будет потокобезопасной, поскольку доступ к подсчету ссылок отдельных string не будет защищен. Вы должны избегать таких трюков, как черт. Либо полагайтесь на классическое циклическое копирование (медленнее, но безопаснее), либо, что еще лучше, измените алгоритм доступа к данным, чтобы сделать его еще быстрее. - person Arnaud Bouchez; 18.08.2011
comment
@Arnand: Хорошо, часть перемещения и заполнения может быть защищена замком. Я сказал, что это был быстрый и грязный взлом. В остальном я не вижу в этом проблемы, не более, чем например. используя низкоуровневый _CopyRecord. - person Rudy Velthuis; 18.08.2011
comment
@David: идиома подкачки гарантирует, что это также безопасно для типов и что ничего плохого не может произойти. Он не полагается на знание внутреннего устройства. Арнанд прав в том, что комбинация move/fillchar должна быть атомарной, иначе в многопоточной среде могут возникнуть проблемы. - person Rudy Velthuis; 18.08.2011
comment
Ваша первоначальная версия отличалась, нет? указатель()? Все кажется мне совершенно бессмысленным. Используйте цикл for и простое присваивание. Узкое место в производительности всегда где-то в другом месте. - person David Heffernan; 18.08.2011
comment
@Rudy _CopyRecord будет копировать каждое поле с подсчетом ссылок одно за другим, используя _LStrAsg/ULStrasg/_WStrAsg/_IntfCopy потокобезопасным способом. Накладные расходы скорости не будут катастрофическими. Что касается защиты от блокировки, этого было бы недостаточно в случае подсчета ссылок на отдельные строки, насколько мне известно. - person Arnaud Bouchez; 18.08.2011
comment
@David, я согласен с вами, что решение цикла является самым четким, и даже если оно не самое быстрое, оно достаточно быстрое. На самом деле, даже наша обертка TDynArray использует такой цикл с использованием RTTI. - person Arnaud Bouchez; 18.08.2011

Вот как я справился с этим, хотя для использования 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 вместо таких вещей, как массовые копии памяти, которые могут вызвать у меня раздражение.

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

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

person jep    schedule 02.07.2013

Ваш код отлично работает в новейшей версии delphi C := A+B;.

Но для динамических массивов в старых версиях можно использовать функцию concat. Пример:

C := Concat(A, B);
person Ruslan Kanteruk    schedule 23.05.2018
comment
Динамическая конкатенация массивов появилась в Delphi XE7. Эта функция была как для синтаксиса Concat(), так и для синтаксиса +. До этого Concat() работал только со строками. См. XE7: что нового, строковые операции, поддерживаемые в Динамические массивы - person LU RD; 23.05.2018
comment
Ответ был бы в порядке, если бы вы перефразировали часть, в которой более старые версии могли использовать Concat для объединения динамических массивов, поскольку это неверно. - person LU RD; 23.05.2018