如何在没有冗余列表的情况下迭代 parent/child 层次结构中的对象?

How to iterate objects in parent/child hierarchy without redundant lists?

我有一个类似这样的对象...

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;

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

这允许对象之间存在简单的 parent/child 关系,即层次结构。一个是根,包含了一层层级的more。

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

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

end;

当然,我可以只创建一个对象列表并同时维护它们...

FAllObjects: TObjectList<TMyObject>;

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

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

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

在标准 TTreeView 中,以 "linear" 方式从上到下遍历其所有节点的最佳方法是在 while循环,例如:

var
  Node: TTreeNode;

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

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

Declarations and Statements (Delphi): Iteration Over Containers Using For statements

例如:

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 循环。

我会以实用的方式解决这个问题,无需修改 (*) 您要遍历的 structure/classes。

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

(*) 关于您的 TMyObject class 它需要以某种方式公开 Childs(我会通过放置 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
  ...