1

我有一个像这样的对象......

type
  TMyObject = class(TObject)
  private
    FParent: TMyObject;
    FChildren: TObjectList<TMyObject>;
    function GetChildren(const Index: Integer): TMyObject;
  public
    constructor Create(AParent: TMyObject);
    destructor Destroy; override;
    function AddChild: TMyObject;
    procedure DeleteChild(const Index: Integer);
    function ChildCount: Integer;
    property Children[const Index: Integer]: TMyObject read GetChildren; default;
  end;

(还有更多,但这是基本思想)

这允许对象之间存在简单的父/子关系,即层次结构。一个是根,它包含更多的层次结构。

这一切都很好,除了我还需要迭代所有这些对象的完整列表,而不考虑层次结构。

var
  Node: TMyObject;
for X := 0 to AllNodes.Count-1 do begin
  Node := AllNodes[X];
  //Do something with `Node`... 

end;

自然地,我可以创建一个对象列表并同时维护两者......

FAllObjects: TObjectList<TMyObject>;

然而,这是多余的。的每个实例TMyObject都必须同时添加/删除到每个结构中。我想摆脱需要一个主列表,而只使用层次结构对象。但我不知道如何在不遵循递归层次结构的情况下迭代所有对象。例如,像获取所有这些项目的总数这样简单的事情。

如何维护这样的对象层次结构(我可以在一个循环中迭代所有项目)不必维护两个单独的冗余结构?

例如,TTreeView.Items具有我想要的行为。您可以使用Items.CountandItems[Index]来迭代所有项目,也可以递归地迭代树层次结构。

4

2 回答 2

4

我会以一种功能性的方式解决这个问题,而无需修改(*)您要遍历的结构/类。

您需要的是根项和获取子项的函数。

(*)关于你的TMyObject班级,它需要以某种方式暴露孩子们(我会通过放一个来做到这一点,property Childs: TEnumerable<TMyObject>以使他们只读。

以下是基本遍历任何非多态层次结构的预排序方法:

unit HierarchyEnumerator;

interface

uses
  Generics.Collections,
  SysUtils;

type
  THierarchyEnumerable<T> = record
  private
    fItems: TEnumerable<T>;
    fChildSelector: TFunc<T, TEnumerable<T>>;

    type
      TEnumerator = class
      private
        fStack: TStack<TEnumerator<T>>;
        fChildSelector: TFunc<T, TEnumerable<T>>;
        fCurrent: T;
      public
        constructor Create(const items: TEnumerable<T>; const childSelector: TFunc<T, TEnumerable<T>>);
        destructor Destroy; override;
        function MoveNext: Boolean;
        property Current: T read fCurrent;
      end;
  public
    constructor Create(const items: TEnumerable<T>; const childSelector: TFunc<T, TEnumerable<T>>);
    function GetEnumerator: TEnumerator;
  end;

implementation

{ THierarchyEnumerable<T> }

constructor THierarchyEnumerable<T>.Create(const items: TEnumerable<T>;
  const childSelector: TFunc<T, TEnumerable<T>>);
begin
  fItems := items;
  fChildSelector := childSelector;
end;

function THierarchyEnumerable<T>.GetEnumerator: TEnumerator;
begin
  Result := TEnumerator.Create(fitems, fChildSelector);
end;

{ THierarchyEnumerable<T>.TEnumerator }

constructor THierarchyEnumerable<T>.TEnumerator.Create(const items: TEnumerable<T>;
  const childSelector: TFunc<T, TEnumerable<T>>);
var
  item: T;
begin
  inherited Create;
  fStack := TStack<TEnumerator<T>>.Create;
  fStack.Push(items.GetEnumerator);
  fChildSelector := childSelector;
end;

destructor THierarchyEnumerable<T>.TEnumerator.Destroy;
begin
  fStack.Free;
  inherited;
end;

function THierarchyEnumerable<T>.TEnumerator.MoveNext: Boolean;
var
  e: TEnumerator<T>;
begin
  while fStack.Count > 0 do
  begin
    e := fStack.Pop;
    if e.MoveNext then
    begin
      fStack.Push(e);
      fCurrent := e.Current;
      fStack.Push(fChildSelector(fCurrent).GetEnumerator);
      Exit(True);
    end
    else
      e.Free;
  end;

  Result := False;
end;

end.

使用看起来像这样:

for o in THierarchyEnumerable<TMyObject>.Create(list,
  function(item: TMyObject): TEnumerable<TMyObject>
  begin
    Result := item.Children;
  end) do
  ...
于 2017-05-24T09:11:53.373 回答
2

在标准TTreeView中,以“线性”方式从上到下迭代所有节点的最佳方法是TTreeNode.GetNext()在循环中使用该方法while,例如:

var
  Node: TTreeNode;

Node := TreeView.GetFirstNode;
while Node <> nil do
begin
  //Do something with Node... 
  Node := Node.GetNext;
end;

在您的自定义节点列表中,您可以通过实现可与for..in循环一起使用的 Enumerator 来实现类似的迭代,该循环在 Delphi 2007 中引入。有关更多详细信息,请参阅 Embarcadero 的文档:

声明和语句 (Delphi):使用 For 语句对容器进行迭代

例如:

type
  TMyObject = class(TObject)
  private
    FParent: TMyObject;
    FChildren: TObjectList<TMyObject>;
  public
    constructor Create(AParent: TMyObject);
    destructor Destroy; override;
    function PreviousSibling: TMyObject;
    function NextSibling: TMyObject;
    function FirstChild: TMyObject;
    property Parent: TMyObject read FParent;
  end;

function TMyObject.PreviousSibling: TMyObject;
var
  Index: Integer;
begin
  Result := nil;
  if FParent <> nil then
  begin
    Index := FParent.FChildren.IndexOf(Self);
    if Index > 0 then
      Result := FParent.FChildren[Index-1];
  end;
end;

function TMyObject.NextSibling: TMyObject;
var
  Index: Integer;
begin
  Result := nil;
  if FParent <> nil then
  begin
    Index := FParent.FChildren.IndexOf(Self);
    if (Index >= 0) and (Index < (FParent.FChildren.Count-1)) then
      Result := FParent.FChildren[Index+1];
  end;
end;

function TMyObject.FirstChild: TMyObject;
begin
  if FChildren.Count > 0 then
    Result := FChildren.First
  else
    Result := nil;
end;

type
  TMyListEnumerator = class
  private
    FList: TMyList;
    FCurrent: TMyObject;
  public
    constructor Create(AList : TMyList);
    function MoveNext: Boolean;
    property Current: TMyObject read FCurrent;
  end;

  TMyList = class
  private
    FRoot: TMyObject;
  public
    function GetEnumerator: TMyListEnumerator;
  end; 

constructor TMyListEnumerator.Create(AList: TMyList);
begin
  inherited Create;
  FList := AList;
  FCurrent := nil;
end;

function TMyListEnumerator.MoveNext: Boolean;
var
  LObject, LParent: TMyObject;
begin
  if FCurrent = nil then begin
    FCurrent := FList.FRoot;
  end else
  begin
    LObject := FCurrent.FirstChild;
    if LObject = nil then
      LObject := FCurrent.NextSibling;
    LParent := FCurrent;
    while (LObject = nil) and (LParent <> nil) do
    begin
      LParent := LParent.Parent;
      LObject := LParent.NextSibling;
    end;
    FCurrent := LObject;
  end;
  Result := FCurrent <> nil;
end;

function TMyList.GetEnumerator: TMyListEnumerator;
begin
  Result := TMyListEnumerator.Create(Self);
end;

var
  MyList: TMyList;
  Node: TMyObject;

// populate MyList as needed...

for Node in MyList do
begin
  //Do something with Node... 
end;

在后台,编译器将生成类似于以下的代码:

var
  MyList: TMyList;
  Node: TMyObject;
  Enum: TMyListEnumerator;

// populate MyList as needed...

Enum := MyList.GetEnumerator;
try
  while Enum.MoveNext do
  begin
    Node := Enum.Current;
    //Do something with Node... 
  end;
finally
  Enum.Free;
end;

如果您使用的是 Delphi 2006 或更早版本,for..in则不可用,因此您必须while明确使用上述循环。

于 2017-05-23T21:58:18.467 回答