Как хранить динамические массивы в TList? - PullRequest
6 голосов
/ 04 марта 2011

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

 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 ]

6 голосов
/ 04 марта 2011

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

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

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

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

4 голосов
/ 07 марта 2011

Я создал оболочку для динамического массива 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 будет работать также с массивом строк или массивов записей ... Записи должны быть только упакованы и иметь только поля со счетчиком ссылок (байтов, целых, двойных ...) или ссылки на строки-counts поля (без Variant или Interface внутри).

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

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

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

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

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

Редактировать:

Я добавил несколько новых методов в запись / объект 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 ...

4 голосов
/ 04 марта 2011

Еще одна вещь, которую вы можете попробовать, это использовать 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;

и т.д ...

4 голосов
/ 04 марта 2011

У меня здесь нет 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.
1 голос
/ 04 марта 2011

Когда вы кодируете:

 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 ... Groups [i] будет равен GroupArray [] ... Без проблем с подсчетом ссылок ... Потому что, например, если вы изменили размер дляэкземпляр элемента TGroup из его указателя в Groups [], я не уверен, что у вас не будет утечки памяти ...

0 голосов
/ 04 марта 2011

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

...