3

ロブ・ケネディ氏が示唆したように、VCLコンポーネントへのデータの保存を停止し、「基礎となるデータ構造」を持たせる必要があるところまで来ました。

まず、この質問は「基礎となるデータ構造を作成するにはどうすればよいか」についてです。:)

私の階層は2つのレベルのノードで構成されています。

今、私はルートノードをループすることによって自分のものを通り抜けます。ルートノードの子ノードをループして、必要なもの(データ)を取得します。スレッドを使用してエントリを簡単に変更できるように、すべてのデータをいわゆる基になるデータ構造に格納できるようにしたいと思います(それができると思いますか?)

ただし、エントリをループする場合(現在)、結果はノードのチェック状態によって異なります。基になるデータ構造を使用している場合、ノードがチェックされているかどうかを確認するには、データ構造をループするときに、私のノードではありませんか?

2つのレベルを使用したいとしましょう。

これは親になります:

TRoot = Record
  RootName : String;
  RootId : Integer;
  Kids : TList; //(of TKid)
End;

そして子供:

TKid = Record
  KidName : String;
  KidId : Integer;
End;

それは基本的に私が今していることです。コメントは、これが最善の解決策ではないと述べているので、私は提案を受け入れています。:)

私の質問をご理解いただければ幸いです。:)

ありがとう!

4

6 に答える 6

9

要求しているデータ構造は非常に単純です。非常に単純なので、Windowsが提供するものをTTreeView使用することをお勧めします。追加の作業なしでテキストとIDをツリーのノードに直接保存できます。


より単純なものを使用することをお勧めしますがTTreeView、データ構造の問題についての私の見解を示します。まず、レコードではなく、クラスを使用します。非常に短いコードサンプルでは、​​非常に不規則な方法でレコードとクラスを混合しています。レコードのコピーを作成する場合(レコードは常に「値」として扱われるため、レコードを割り当てると完全なコピーが作成されます)、作成しません。ツリーの「ディープコピー」:レコードとは異なり、クラスは参照であるため、の完全なコピーには元のコピーと同じものが含まれます。参照の値に対処していることになります。TRootTRootKids:TList

オブジェクトフィールドを持つレコードがある場合のもう1つの問題は、ライフサイクル管理です。レコードにはデストラクタがないため、所有しているオブジェクトを解放するために別のメカニズムが必要になります(Kids:TList)。TListをに置き換えることもできますが、モンスターレコードを渡すときは、非常にarray of Tkid注意する必要があります。これは、予想外のときに巨大なレコードのディープコピーを作成しなくなる可能性があるためです。

私の意見では、最も賢明なことは、レコードではなくクラスに基づいてデータ構造を作成することです。クラスインスタンス(オブジェクト)は参照として渡されるため、問題なく必要な場所に移動できます。また、組み込みのライフサイクル管理(デストラクタ)を取得します

基本クラスは次のようになります。ルートとキッドの両方がデータを共有するため、ルートまたはキッドのいずれかとして使用できることに気付くでしょう。どちらにも名前とIDがあります。

TNodeClass = class
public
  Name: string;
  ID: Integer;
end;

このクラスをルートとして使用する場合は、キッズを保存する方法が必要です。Delphi 2010以降を使用していると思いますので、ジェネリックを使用しています。リストを備えたこのクラスは、次のようになります。

type
  TNode = class
  public
    ID: integer;
    Name: string;
    VTNode: PVirtualNode;
    Sub: TObjectList<TNode>;

    constructor Create(aName: string = ''; anID: integer = 0);
    destructor Destroy; override;
  end;

constructor TNode.Create(aName:string; anID: Integer);
begin
  Name := aName;
  ID := anID;

  Sub := TObjectList<TNode>.Create;
end;

destructor TNode.Destroy;
begin
  Sub.Free;
end;

これにすぐには気付かないかもしれませんが、このクラスだけでマルチレベルツリーを実装できます。ツリーをデータで埋めるためのコードを次に示します。

Root := TNode.Create;

// Create the Contacts leaf
Root.Sub.Add(TNode.Create('Contacts', -1));
// Add some contacts
Root.Sub[0].Sub.Add(TNode.Create('Abraham', 1));
Root.Sub[0].Sub.Add(TNode.Create('Lincoln', 2));

// Create the "Recent Calls" leaf
Root.Sub.Add(TNode.Create('Recent Calls', -1));
// Add some recent calls
Root.Sub[1].Sub.Add(TNode.Create('+00 (000) 00.00.00', 3));
Root.Sub[1].Sub.Add(TNode.Create('+00 (001) 12.34.56', 4));

このタイプを使用して仮想ツリービューを埋めるには、再帰的な手順が必要です。

procedure TForm1.AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
var SubNode: TNode;
    ThisNode: PVirtualNode;

begin
  ThisNode := VT.AddChild(ParentNode, Node); // This call adds a new TVirtualNode to the VT, and saves "Node" as the payload

  Node.VTNode := ThisNode; // Save the PVirtualNode for future reference. This is only an example,
                           // the same TNode might be registered multiple times in the same VT,
                           // so it would be associated with multiple PVirtualNode's.

  for SubNode in Node.Sub do
    AddNodestoTree(ThisNode, SubNode);
end;

// And start processing like this:
VT.NodeDataSize := SizeOf(Pointer); // Make sure we specify the size of the node's payload.
                                    // A variable holding an object reference in Delphi is actually
                                    // a pointer, so the node needs enough space to hold 1 pointer.
AddNodesToTree(nil, Root);

オブジェクトを使用する場合、仮想ツリーのノードごとに異なるタイプのオブジェクトが関連付けられている場合があります。この例では、タイプのノードのみを追加していTNodeますが、実際には、タイプ、、、のノードがTContactすべてTContactCategory1TRecentCallつのVTに含まれている場合があります。演算子を使用して、is次のようにVTノード内のオブジェクトの実際のタイプを確認します。

procedure TForm1.VTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var PayloadObject:TObject;
    Node: TNode;
    Contact : TContact;      
    ContactCategory : TContactCategory;
begin
  PayloadObject := TObject(VT.GetNodeData(Node)^); // Extract the payload of the node as a TObject so
                                                   // we can check it's type before proceeding.
  if not Assigned(PayloadObject) then
    CellText := 'Bug: Node payload not assigned'
  else if PayloadObject is TNode then
    begin
      Node := TNode(PayloadObject); // We know it's a TNode, assign it to the proper var so we can easily work with it
      CellText := Node.Name;
    end
  else if PayloadObject is TContact then
    begin
      Contact := TContact(PayloadObject);
      CellText := Contact.FirstName + ' ' + Contact.LastName + ' (' + Contact.PhoneNumber + ')';
    end
  else if PayloadObject is TContactCategory then
    begin
      ContactCategory := TContactCategory(PayloadObject);
      CellText := ContactCategory.CategoryName + ' (' + IntToStr(ContactCategory.Contacts.Count) + ' contacts)';
    end
  else
    CellText := 'Bug: don''t know how to extract CellText from ' + PayloadObject.ClassName;
end;

そして、ノードインスタンスへのVirtualNodeポインタを保存する理由の例を次に示します。

procedure TForm1.ButtonModifyClick(Sender: TObject);
begin
  Root.Sub[0].Sub[0].Name := 'Someone else'; // I'll modify the node itself
  VT.InvalidateNode(Root.Sub[0].Sub[0].VTNode); // and invalidate the tree; when displayed again, it will
                                                // show the updated text.
end;

単純なツリーデータ構造の実用的な例があることをご存知でしょう。ニーズに合わせてこのデータ構造を「成長」させる必要があります。可能性は無限大です。あなたにいくつかのアイデアを与えるために、探求する方向:

  • Name:string仮想メソッドに変換してから、そのオーバーライドGetText:string;virtualの特殊な子孫を作成して、特殊な動作を提供できます。TNodeGetText
  • TNode.AddPath(Path:string; ID:Integer)これを可能にするを作成しますRoot.AddPath('Contacts\Abraham', 1);。つまり、ツリーを簡単に作成できるように、すべての中間ノードを最終ノードに自動的に作成するメソッドです。
  • ノードが仮想ツリーで「チェック」されているかどうかをチェックできるように、それ自体にインクルードしますPVirtualNodeTNodeこれは、データとGUIの分離の架け橋になります。
于 2011-03-20T19:39:38.373 に答える
4

ここで同様の質問をしました。有用な答えが得られなかったので、ここで見つけることができる独自の実装を作成することにしました。

編集:私のデータ構造をどのように使用できるかの例を投稿しようと思います:

uses
  svCollections.GenericTrees;

データ型を宣言します。

type
  TMainData = record
    Name: string;
    ID: Integer;
  end;

コードのどこかでメインデータツリーオブジェクトを宣言します。

MyTree: TSVTree<TMainData>;

それを作成します(そして後で解放することを忘れないでください):

MyTree: TSVTree<TMainData>.Create(False);

VirtualStringTreeをデータ構造に割り当てます。

MyTree.VirtualTree := VST;

次に、いくつかの値を使用してデータツリーを初期化できます。

procedure TForm1.BuildStructure(Count: Integer);
var
  i, j: Integer;
  svNode, svNode2: TSVTreeNode<TMainData>;
  Data: TMainData;
begin
  MyTree.BeginUpdate;
  try
    for i := 0 to Count - 1 do
    begin
      Data.Name := Format('Root %D', [i]);
      Data.ID := i;
      svNode := MyTree.AddChild(nil, Data);
      for j:= 0 to 10 - 1 do
      begin
        Data.Name := Format('Child %D', [j]);
        Data.ID := j;
        svNode2 := MyTree.AddChild(svNode, Data);
      end;
    end;
  finally
    MyTree.EndUpdate;
  end;
end;

そして、データを表示するようにVSTイベントを設定します。

procedure TForm1.vt1InitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode;
  var ChildCount: Cardinal);
var
  svNode: TSVTreeNode<TMainData>;
begin
  svNode := MyTree.GetNode(Sender.GenerateIndex(Node));
  if Assigned(svNode) then
  begin
    ChildCount := svNode.FChildCount;
  end;
end;

procedure TForm1.vt1InitNode(Sender: TBaseVirtualTree; ParentNode,
  Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
  svNode: TSVTreeNode<TMainData>;
begin
  svNode := MyTree.GetNode(Sender.GenerateIndex(Node));
  if Assigned(svNode) then
  begin
    //if TSVTree<TTestas> is synced with Virtual Treeview and we are building tree by
    // setting RootNodeCount, then we must set svNode.FVirtualNode := Node to
    // have correct node references
    svNode.FVirtualNode := Node;  // Don't Forget!!!!
    if svNode.HasChildren then
    begin
      Include(InitialStates, ivsHasChildren);
    end;
  end;
end;

//display info how you like, I simply get name and ID values
procedure TForm1.vt1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  svNode: TSVTreeNode<TMainData>;
begin
  svNode := MyTree.GetNode(Sender.GenerateIndex(Node));
  if Assigned(svNode) then
  begin
    CellText := Format('%S ID:%D',[svNode.FValue.Name, svNode.FValue.ID]);
  end;
end;

この時点では、MyTreeデータ構造のみを操作し、それに加えられたすべての変更は、割り当てられたVSTに反映されます。その後、基になる構造をいつでもストリームまたはファイルに保存(およびロード)できます。お役に立てれば。

于 2011-03-19T23:10:08.647 に答える
4

一般的なツリー実装を含む既存のライブラリを見つけて、ニーズに対応するために再利用できるようにすることで、最善のサービスが提供されると思います。

理由を理解するために、考えられる最も単純なツリー構造での最も単純な操作を説明するために私が書いたコードを次に示します。

type
  TNode = class
    Parent: TNode;
    NextSibling: TNode;
    FirstChild: TNode;
  end;

  TTree = class
    Root: TNode;
    function AddNode(Parent: TNode): TNode;
  end;

function TTree.AddNode(Parent: TNode);
var
  Node: TNode;
begin
  Result := TNode.Create;

  Result.Parent := Parent;
  Result.NextSibling := nil;
  Result.FirstChild := nil;

  //this may be the first node in the tree
  if not Assigned(Root) then begin
    Assert(not Assigned(Parent));
    Root := Result;
    exit;
  end;

  //this may be the first child of this parent
  if Assigned(Parent) and not Assigned(Parent.FirstChild) then begin
    Parent.FirstChild := Result;
  end;

  //find the previous sibling and assign its next sibling to the new node
  if Assigned(Parent) then begin
    Node := Parent.FirstChild;
  end else begin
    Node := Root;
  end;
  if Assigned(Node) then begin
    while Assigned(Node.NextSibling) do begin
      Node := Node.NextSibling;
    end;
    Node.NextSibling := Result;
  end;
end;

:私はこのコードをテストしていないため、その正確性を保証することはできません。欠陥があると思います。

これは、ツリーに新しいノードを追加するだけです。ツリーのどこにノードを追加するかをほとんど制御できません。指定された親ノードの最後の兄弟として新しいノードを追加するだけの場合。

この種のアプローチを取るには、おそらく次のことに対処する必要があります。

  • 指定された兄弟の後に挿入します。実際、これは上記の非常に単純な変形です。
  • ノードを削除します。これはやや複雑です。
  • ツリー内の既存のノードを移動します。
  • 木を歩く。
  • ツリーをVSTに接続します。

これを行うことは確かに実行可能ですが、すでに機能を実装しているサードパーティのライブラリを見つけることをお勧めします。

于 2011-03-20T17:17:07.420 に答える
2

私が正しく理解していれば、ツリーのデータ構造が必要です。個々のノードには、データを保持するためのレコードが必要です。ただし、基礎となる階層は、いくつかの異なる方法で管理できます。これはすべて、ある種のデータベースで管理されると思います-これはすでにこのサイトで話し合われているので、次のことを指摘します。

データベースに階層データ構造を実装する

そしてここ:

フラットテーブルをツリーに解析するための最も効率的でエレガントな方法は何ですか?

そしてここ:

SQL-階層を保存してナビゲートする方法は?

入れ子集合モデル:

http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

于 2011-03-20T00:22:18.600 に答える
0

GenericsをサポートするDelphiの最新バージョンを使用している場合は、GenericTreeを確認してください

于 2016-07-27T09:31:04.613 に答える
-1

Delphiには最近ジェネリックがあります。非常に優れたツリーデータ構造を発明しました。まだコードを提供するつもりはありません。実際にはオープンソースの人ではありませんが、おそらく近い将来、他の理由も以下を参照してください。

しかし、私はそれを再作成する方法についていくつかのヒントを与えます:

すべてのノードに同じデータ構造(上記の場合のように見えます)、文字列、ID、およびリンクを含めることができると仮定します。

これを再現するために必要な材料は次のとおりです。

  1. ジェネリック
  2. ジェネリック型T
  3. このタイプTは、次のようにクラスとコンストラクターに制約する必要があります。

<T:クラス、コンストラクター>(この場合、クラスをレコードに置き換えます。テストされていませんが、機能する場合もあります)

  1. 2つのフィールド:自己のノード配列(ヒントヒント)、データ:T;

  2. プロパティ

  3. プロパティだけでなく、デフォルトのプロパティ;)

  4. ゲッター。

  5. 深さと子を持つ再帰コンストラクター。

  6. 建設を停止するためのいくつかのifステートメント。

  7. そしてもちろん、SetLengthを使用してリンク/ノードを作成し、forループでいくつかの作成を呼び出してから、何かを減算します;)

皆さんに十分なヒントがあれば、誰かがそれを再現できるかどうかを確認するのは楽しくて面白いでしょう。そうでなければ、他の施設を使ってもクラスを拡大するかもしれません。

クラスは、実際のデータ構造のように構築中にすべてのノードを割り当てます...少なくとも今のところは、追加や削除などに注意してください。

今、この(秘密の)デザインの最も面白くて面白い側面がやって来ます。私がちょっと欲しかったもので、今では現実になっています。これで、次のようにコードを記述できます。

TGroupは単なる例であり、私の場合はクラスであれば何でもかまいません。この場合、それはmStringを持つ単なるクラスです

var
  mGroupTree : TTree<TGroup>;

procedure Main;
var
  Depth : integer;
  Childs : integer;
begin

  Depth := 2;
  Childs := 3;

  mGroupTree := TTree<TGroup>.Create( Depth, Childs );

  mGroupTree.Data.mString := 'Basket'; // notice how nice this root is ! ;)

  mGroupTree[0].Data.mString := 'Apples';
  mGroupTree[1].Data.mString := 'Oranges';
  mGroupTree[2].Data.mString := 'Bananas';

  mGroupTree[0][0].Data.mString := 'Bad apple';
  mGroupTree[0][1].Data.mString := 'Average apple';
  mGroupTree[0][2].Data.mString := 'Good apple';

  mGroupTree[1][0].Data.mString := 'Tiny orange';
  mGroupTree[1][1].Data.mString := 'Medium orange';
  mGroupTree[1][2].Data.mString := 'Big orange';

  mGroupTree[2][0].Data.mString := 'Straight banana';
  mGroupTree[2][1].Data.mString := 'Curved banana';
  mGroupTree[2][2].Data.mString := 'Crooked banana';

この実際のテストコードから気付くかもしれませんが、このプロパティのおかげで、私がめったに見たことがないような「配列拡張」が可能になります。

したがって、[][]は深さ2です。[][][]は深さ3になります。

私はまだこれの使用を評価しています。

潜在的な問題の1つは、Delphiにはこれらの配列を自動拡張する実際の手法がないことですが、私がまだ見つけて統計化したものはありません。

任意の深度レベルに到達できるコードを記述できる手法が必要です。

[0] [0] [0] [0] [0]

それを行う方法はまだわかりません...simpelstオプションは「再帰」です。

実際の例:

procedure DisplayString( Depth : string; ParaTree : TTree<TGroup>);
var
  vIndex : integer;
begin
  if ParaTree <> nil then
  begin
//    if ParaTree.Data.mString <> '' then
    begin
      writeln( ParaTree.Data.mString );

      Depth := Depth + ' ';
      for vIndex := 0 to ParaTree.Childs-1 do
      begin
        DisplayString( Depth, ParaTree[vIndex] );
      end;
    end;
  end;
end;

ちょっとおもしろいですね。

それは「実際のアプリケーション」にとって有用であり、再帰を使用するかどうかを検討し続けています;)

多分いつか私は私のすべてのコードをオープンソースにするでしょう。私は40歳に近く、40歳を超えて39歳から40歳になると、オープンソースになることを計画していました。40=Dからまだ4か月

(ジェネリックスに感銘を受け、ずっと前にテストしたのはこれが初めてだと言わなければなりません。当時は非常にバグが多く、デザイン的には使用できなかったかもしれませんが、バグが修正され、ジェネリックスが制約されているため、最新のDelphiToykoでは非常に印象的です。 10.2.3バージョン2018年8月!;):))

最新のDelphi技術では不可能なことの表面をかじっただけです。おそらく、匿名メソッドを使用して、このデータ構造を処理する再帰ルーチンを作成するのが少し簡単になるかもしれません。また、並列処理が考慮されるかもしれません。Delphiヘルプは、匿名メソッドについてこれについて言及しています。

さようなら、スカイバック。

于 2018-08-08T22:07:54.717 に答える