Как хранить динамические массивы в TList?

мне нужно сохранить неизвестное количество групп. Каждая группа имеет неизвестное количество элементов / элементов. Это моя "группа":

 TGroup= array of Integer;     <------ dynamic array (as you can see) :)

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

у меня есть этот код, но я не могу заставить его работать:

TYPE
   TGroup= array of Integer;                              // Each group has x items (x can be from 1 to 10000)


procedure TForm1.FormCreate(Sender: TObject);
VAR CurGroup: TGroup;
    grp, item: Integer;
    Groups: TList;                                        // can contain up to 1 million groups
begin
 Groups:= TList.Create;

 { Init }
 for grp:= 1 to 4  DO                                     // Put a dummy item in TList
  begin
   SetLength(CurGroup, 1);                                // Create new group
   Groups.Add(@CurGroup);                                 // Store it
  end;

 CurGroup:= NIL;                                          // Prepare for next use

 for grp:= 1 to 4  DO                                     // We create 4 groups. Each group has 3 items
  begin
    CurGroup:= Groups[Groups.Count-1];                    // We retrieve the current group from list in order to add more items to it

    { We add few items }
    for item:= 0 to 2  DO
     begin
       SetLength(CurGroup, Length(CurGroup)+1);           // reserve space for each new item added
       CurGroup[item]:= Item;
     end;

    Groups[Groups.Count-1]:= @CurGroup;                   // We put the group back into the list
  end;

 { Verify }
 CurGroup:= NIL;
 CurGroup:= Groups[0];
 Assert(Length(CurGroup)> 0);                             // FAIL
 if  (CurGroup[0]= 0)
 AND (CurGroup[1]= 1)
 AND (CurGroup[2]= 2)
 then Application.ProcessMessages;                        

 FreeAndNil(Groups);
end;

Примечание: код завершения. Вы можете вставить его в свой Delphi (7), чтобы попробовать.

6 ответов


Я создал оболочку вокруг динамического массива RTTI.

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

type
  TGroup: array of integer;

var 
  Group: TGroup;
  GroupA: TDynArray;
  i, v: integer;
begin
  GroupA.Init(TypeInfo(TGroup),Group); // associate GroupA with Group
  for i := 0 to 1000 do begin
    v := i+1000; // need argument passed as a const variable
    GroupA.Add(v);
  end;
  v := 1500;
  if GroupA.IndexOf(v)<0 then // search by content
    ShowMessage('Error: 1500 not found!');
  for i := GroupA.Count-1 downto 0 do
    if i and 3=0 then
      GroupA.Delete(i); // delete integer at index i
end;

этой TDynArray wrapper будет работать также с массивом строк или массивом записей... Записи должны быть только упакованы и иметь только не ссылочные подсчитанные поля (byte, integer, double...) или строковые поля, подсчитанные по ссылкам (без варианта или интерфейса внутри).

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

посмотреть TDynArray на в SynCommons.pas unit из нашего репозитория исходного кода. Работает от Delphi 6 до XE и обрабатывает строки Unicode.

и TTestLowLevelCommon._TDynArray метод-это автоматизированные унитарные тесты, связанные с этой оболочкой. Вы найдете здесь образцы массива записей и более продвинутые особенности.

в настоящее время я реализую SaveToStream и LoadToStream методы...

возможно, новый способ использования универсальных функций во всех версиях Delphi.

Edit:

я добавил несколько новых методов к TDynArray запись/объекта:

  • теперь вы можете сохранить и загрузить содержимое динамического массива в строку или из строки (используя LoadFromStream/SaveToStream или LoadFrom/SaveTo методы) - он будет использовать собственный, но очень быстрый двоичный поток макет;
  • и вы можете сортировать содержимое динамического массива двумя способами: либо на месте (т. е. содержимое элементов массива обменивается), либо с помощью внешнего массива поиска целых индексов (используя CreateOrderedIndex метод-в этом случае у вас может быть несколько заказов на одни и те же данные);
  • вы можете указать любую пользовательскую функцию сравнения, и есть новый Find метод может использовать быстрый двоичный поиск, если он доступен.

вот как эти новые методы работа:

var
  Test: RawByteString;
...
  Test := GroupA.SaveTo;
  GroupA.Clear;
  GroupA.LoadFrom(Test);
  GroupA.Compare := SortDynArrayInteger;
  GroupA.Sort;
  for i := 1 to GroupA.Count-1 do
    if Group[i]<Group[i-1] then
      ShowMessage('Error: unsorted!');
  v := 1500;
  if GroupA.Find(v)<0 then // fast binary search
    ShowMessage('Error: 1500 not found!');

еще ближе к общей парадигме, быстрее и для Delphi 6 до XE...


О, это было бы намного приятнее в новых версиях Delphi... Вы бы использовали общий, TList. var группы: TList;

лучше всего использовать другой динамический массив: группы: массив TGroup;

причина в том, что динамические массивы управляются компилятором и подсчитываются ссылки. TList работает только с указателями. Нет прямого способа сохранить ссылочные отсчеты в здравом уме при попытке поместить dynarrays в TList.

другой проблема в том, что вы добавляете адреса стека переменной динамического массива в TList, а не фактический массив. Выражение @CurGroup - это "адрес переменной CurGroup", которая является локальной переменной, находится в стеке.


У меня нет D7 на машине здесь, но вы можете попробовать что - то вроде этого (консольное тестовое приложение-оно компилируется в XE без подсказок или предупреждений, но не уверен, как D7 будет обрабатывать его):

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
  TGroup = array of Integer;
  THolder=class(TObject)
    Group: TGroup;
  end;

var
  GroupList: TList;
  i: Integer;

begin
  GroupList := TList.Create;
  for i := 0 to 2 do
  begin
    GroupList.Add(THolder.Create);
    with THolder(GroupList[GroupList.Count - 1]) do
    begin
      SetLength(Group, 3);
      Group[0] := i;
      Group[1] := i + 10;
      Group[2] := i + 20;
    end;
  end;
  for i := 0 to GroupList.Count - 1 do
  begin
    Writeln(i, ':0 ', THolder(GroupList[i]).Group[0]);
    Writeln(i, ':1 ', THolder(GroupList[i]).Group[1]);
    Writeln(i, ':2 ', THolder(GroupList[i]).Group[2]);
  end;
  // Not cleaning up list content, because we're exiting the app.
  // In a real app, of course, you'd want to free each THolder in 
  // the list, or use a TObjectList and let it own the objects.
  // You'd also want a try..finally to make sure the list gets freed.
  GroupList.Free;
  Readln;
end.

еще одна вещь, которую вы можете попробовать, - это использовать TObjectList. TObjectList может хранить список объектов, поэтому можно создать новый потомок класса TObject, а затем управлять TObjectList с помощью этих объектов.

что-то вроде (непроверенных):

type TGroupArray : array of Integer;

type TGroup = class(Tobject)
  GroupArray : TGroupArray;
end;

GroupList : TobjectList;

procedure TForm1.FormCreate(Sender: TObject);                                        
var CurGroup : TGroup;
begin  

GroupList:= TObjectList.Create; 
CurGroup := TGroup.Create;
SetLength(CurGroup.GroupArray,1);
CurGroup.GroupArray[0] := 10;

GroupList.Add(CurGroup);

RetreiveGroup := GroupList.Items[0];
FreeandNil(GroupList);
end;

etc...


когда вы код:

 for grp:= 1 to 4  DO                                     // Put a dummy item in TList
  begin
   SetLength(CurGroup, 1);                                // Create new group
   Groups.Add(@CurGroup);                                 // Store it
  end;

фактически, SetLength (CurGroup) не создаст новую группу. Он изменит размер только одной существующей CurGroup.

поэтому @CurGroup не изменится: это всегда будет какой-то адрес в стеке, где добавлена CurGroup. Один и тот же адрес добавляется в список несколько раз.

поэтому вам придется создать динамический массив экземпляров TGroup, например:

var GroupArray: array of TGroup;

SetLength(GroupArray,4);
for grp := 0 to high(GroupArray) do
begin
  SetLength(GroupArray[grp],1);
  Groups.Add(@GroupArray[grp]);
end;

но, конечно, GroupArray должен оставаться выделенным во время все временные группы будут нуждаться в этом. Поэтому вам, возможно, придется поместить этот GroupArray как свойство в класс, потому что если вы создадите этот GroupArray в стеке, все элементы GroupArray[] будут освобождены, когда метод выйдет и его стек будет освобожден.

но, конечно, GroupArray[] будет более прямым доступом к элементам TGroup... Группы[i] будут равны GroupArray []... Без проблемы подсчета ссылок... Так, например, при изменении размера, например, TGroup элемента из Указателя в группах [] я не уверен, что у вас не будет утечки памяти...


Так, в основном все предлагает создать массив массива вместо использования TList. Ну, я уже сделал это. Я просто хотел "обновить" от "массива массива" до TList, так как он имеет Add(). Похоже, я вернусь к исходному коду. Спасибо всем и +1 за каждый ответ.