Класс TEctoSoftTree представляет собой невизуальное дерево для манипулирования древоподобными структурами в памяти. Мной в очередной раз из любви к искусству
был изобретен велосипед :))), который тем не менее получился вполне съедобным и несмотря на наличие других вариантов решения задачи будет использоваться мной
хотя бы назло врагам :) Буду рад если еще кому-то он придется по вкусу. Просьба при внесении изменений и дополнений в код, а также обнаружении ошибок
(которых здесь нет ;) уведомить автора, т.е. меня
Малышев Владимир aka "мыш"
Code: |
Unit EctoSoftTree;
{===============================================================================
================================================================================}
interface
uses SysUtils, {EctoSysUtils,} Classes {EctoTypes,};
{ TEctoTreeNode class --------------------------------------------------------} type TEctoSoftTree = class;
TEctoTreeNode = class(TObject) private FParentNode: TEctoTreeNode;
function GetDescendantCount(): integer; function GetAbsoluteIndex(): integer; function GetChildIndex(): integer; function GetLevel(): integer; function GetPrevSibling(): TEctoTreeNode; function GetNextSibling(): TEctoTreeNode; function GetLastDescendant(): TEctoTreeNode; procedure SetParent(NewParentNode: TEctoTreeNode);
public ParentTree: TEctoSoftTree; Children: TList; Data: Pointer; Caption: string; destructor Destroy(); override; constructor Create();
function GetPrevChild(TargetChildNode: TEctoTreeNode): TEctoTreeNode; function GetNextChild(TargetChildNode: TEctoTreeNode): TEctoTreeNode; function GetLastChild(): TEctoTreeNode; function GetNext(): TEctoTreeNode; function GetPrev(): TEctoTreeNode; function IsRoot(): boolean; function IsParentOf(Node: TEctoTreeNode): boolean;
procedure MoveUp(); procedure MoveDown(); procedure MoveLeft(); procedure MoveRight(); procedure Sort(Compare: TListSortCompare; SortSubtrees: boolean);
property AbsoluteIndex: integer read GetAbsoluteIndex; property Index: integer read GetChildIndex; property PrevSibling: TEctoTreeNode read GetPrevSibling; property NextSibling: TEctoTreeNode read GetNextSibling; property LastDescendant: TEctoTreeNode read GetLastDescendant; property DescendantCount: integer read GetDescendantCount; property Level: integer read GetLevel; property ParentNode: TEctoTreeNode read FParentNode write SetParent; end;
TOnFreeNodeEvent = procedure(Node: TEctoTreeNode) of object;
{ TEctoSoftTree class --------------------------------------------------------} TEctoSoftTree = class(TObject) private FOnFreeNodeEvent: TOnFreeNodeEvent;
function GetNodeFromIndex(Index:integer): TEctoTreeNode; function GetNodeCount(): integer; public Root: TEctoTreeNode; function FindNode(FindCaption: string): TEctoTreeNode; procedure DeleteNode(Index: integer); overload; procedure DeleteNode(DeletingNode: TEctoTreeNode); overload; function AddNode(aParentNode:TEctoTreeNode): TEctoTreeNode; overload; function AddNode(aParentNode:TEctoTreeNode; Caption: string): TEctoTreeNode; overload; function AddNode(aParentNode:TEctoTreeNode; Data: Pointer): TEctoTreeNode; overload; function AddNode(aParentNode:TEctoTreeNode; Caption: string; Data: Pointer): TEctoTreeNode; overload; procedure Clear();
destructor Destroy; override;
property Nodes[Index:integer] : TEctoTreeNode read GetNodeFromIndex; property NodeCount: integer read GetNodeCount; property OnFreeNode: TOnFreeNodeEvent read FOnFreeNodeEvent write FOnFreeNodeEvent; end;
implementation
{ TEctoSoftTree }
function TEctoSoftTree.AddNode(aParentNode: TEctoTreeNode; Caption: string; Data: Pointer): TEctoTreeNode; var NewNode: TEctoTreeNode; begin NewNode := TEctoTreeNode.Create;
if Root=nil then begin NewNode.FParentNode := nil; Root := NewNode; end else begin if aParentNode=nil then Raise EInvalidOperation.Create('Parent node must exists');
NewNode.FParentNode := aParentNode; aParentNode.Children.Add(NewNode); end;
NewNode.Caption := Caption; NewNode.Data := Data; NewNode.ParentTree := self;
result := NewNode; end;
function TEctoSoftTree.AddNode(aParentNode: TEctoTreeNode): TEctoTreeNode; begin result := AddNode(aParentNode,'',nil); end;
function TEctoSoftTree.AddNode(aParentNode: TEctoTreeNode; Caption: string): TEctoTreeNode; begin result := AddNode(aParentNode,Caption,nil); end;
function TEctoSoftTree.AddNode(aParentNode: TEctoTreeNode; Data: Pointer): TEctoTreeNode; begin result := AddNode(aParentNode,'',Data); end;
procedure TEctoSoftTree.Clear; begin if Root=nil then exit; Root.Free; Root := nil; end;
procedure TEctoSoftTree.DeleteNode(Index: integer); begin DeleteNode(Nodes[Index]); end;
procedure TEctoSoftTree.DeleteNode(DeletingNode: TEctoTreeNode); begin if DeletingNode.IsRoot then FreeAndNil(Root) // Рут не нужно исключать из родительского списка, поэтому просто освобождаем else begin DeletingNode.FParentNode.Children.Delete // обращение к ParentNode без проверки на его существование обусловлено тем, что раз это не Root, значит у него обязательно есть Parent (DeletingNode.FParentNode.Children.IndexOf(DeletingNode)); FreeAndNil(DeletingNode); end; end;
destructor TEctoSoftTree.Destroy; begin Clear(); inherited; end;
{ функция FindNode пока ищет только первое вхождение узла с заданным сaption - надо доработать} function TEctoSoftTree.FindNode(FindCaption: string): TEctoTreeNode;
procedure FindNode_(TargetNode: TEctoTreeNode); var i:integer; begin
if result<>nil then exit; // выходим из всех рекурсий, если где-то в одной из них ранее уже был найден узел
{ проверяем вызванный узел TargetNode на соответствие } if TargetNode.Caption = FindCaption then begin result := TargetNode; exit; end; { /проверяем вызванный узел TargetNode на соответствие }
{ вызываем всех детей узела TargetNode для их проверки } i:=0; while i<TargetNode.Children.Count do begin FindNode_(TEctoTreeNode(TargetNode.Children.Items[i])); inc(i); end; { /вызываем всех детей узела TargetNode для их проверки } end;
begin result := nil; FindNode_(Root); end;
function TEctoSoftTree.GetNodeCount: integer; begin if Root=nil then result := 0 else result := Root.GetDescendantCount+1; // +1 - Учитываем Root end;
{ функция GetNodeFromIndex - "движок" для Nodes[Index:integer] } function TEctoSoftTree.GetNodeFromIndex(Index: integer): TEctoTreeNode; var IndexCounter: integer;
procedure CompareNodeIndex(Node: TEctoTreeNode); var i:integer; begin { блок 1 проверяем вызванный узел } inc(IndexCounter); if IndexCounter=Index then begin result := Node; exit; end; { / блок 1 проверяем вызванный узел }
{ вызываем дочерние узлы чтобы выполнить в них предыдущий блок - блок 1 } i:=0; while i<Node.Children.Count do begin CompareNodeIndex(TEctoTreeNode(Node.Children[i])); inc(i); end; { /вызываем дочерние узлы чтобы выполнить в них предыдущий блок - блок 1 } end;
begin IndexCounter := -1; result := nil; CompareNodeIndex(Root); if (result=nil) then Raise EInvalidOperation.Create('Wrong index'); end;
{ TEctoTreeNode }
constructor TEctoTreeNode.Create; begin Children := TList.Create; end;
destructor TEctoTreeNode.Destroy; var i:integer; begin if assigned(ParentTree.FOnFreeNodeEvent) then ParentTree.FOnFreeNodeEvent(self); i:=0; while i<Children.Count do begin TEctoTreeNode(Children.Items[i]).Free; inc(i); end; Children.Free; inherited; end;
function TEctoTreeNode.GetAbsoluteIndex: integer; var Node: TEctoTreeNode; begin if IsRoot then Result := 0 else begin Result := -1; Node := Self; while Node <> nil do begin Inc(Result); Node := Node.GetPrev; end; end; end;
{ функция GetDescendantCount возвращает количество всех потомков данного узла, включая дочерние узлы и их потомки } function TEctoTreeNode.GetChildIndex: integer; begin result := -1; if IsRoot then exit; result := ParentNode.Children.IndexOf(self); end;
function TEctoTreeNode.GetDescendantCount: integer; var Node: TEctoTreeNode; begin result := 0; Node := Self.GetLastDescendant; if Node = nil then exit;
while (Node <> self) do begin inc(result); Node := Node.GetPrev; end; end;
{ функция GetLastChild возвращает последний дочерний узел текущего. Возвращает nil в случае если узел не имеет дочерних узлов, что и обуславливает необходимость данной функции } function TEctoTreeNode.GetLastChild: TEctoTreeNode; begin result := nil; if Children.Count>0 then result := TEctoTreeNode(Children[Children.Count-1]); end;
{ функция GetLastDescendant возвращает последнего потомка текущего узла. Учитываются не только прямые потомки (дочерние узлы) но и дальние (их потомки) } function TEctoTreeNode.GetLastDescendant(): TEctoTreeNode; var Node: TEctoTreeNode; begin Node := self; while Node.GetLastChild<>nil do Node := Node.GetLastChild(); if Node = self then Node := nil; result := Node; end;
function TEctoTreeNode.GetLevel: integer; var Node: TEctoTreeNode; begin result := 0; if IsRoot then exit;
Node := self; while Node<>ParentTree.Root do begin inc(result); Node := Node.FParentNode; end; end;
{ GetNext возвращает следующий узел по ходу "рекурсивного" обхода дерева } function TEctoTreeNode.GetNext: TEctoTreeNode; var Node : TEctoTreeNode; begin result := nil;
if Children.Count>0 then result := TEctoTreeNode(Children[0]); // Если у узла есть дочерние узлы, то следующим за ним будет очевидно первый дочерний
if result = nil then // Если дочерних нет... result := GetNextSibling(); // то следующим будет следующий сестринский узел
if (result = nil) and (not IsRoot) then // Если и дочерних и сестринских нет, а также это не рут, то следующим будет первый сестринский узел родителя begin Node := FParentNode; while (Node.GetNextSibling = nil) and (not Node.IsRoot) do // У родителя может не оказаться сестринских узлов, тогда проводим поиск (идя назад) первого родителя (беря "родителя родителя") у которого будет сестринский узел Node := Node.FParentNode; if not Node.IsRoot then result := Node.GetNextSibling; end; end;
{ функция GetNextChild возвращает следующией дочерний узел отсчитывая от заданного дочернего узла. Если заданный узел является последним дочерним узлом, функция возвращает nil } function TEctoTreeNode.GetNextChild(TargetChildNode: TEctoTreeNode): TEctoTreeNode; var NextChildIndex:integer; begin result := nil; NextChildIndex := Children.IndexOf(TargetChildNode)+1; if (NextChildIndex<Children.Count) and (NextChildIndex>0) then result := TEctoTreeNode(Children[NextChildIndex]); end;
function TEctoTreeNode.GetNextSibling: TEctoTreeNode; begin if IsRoot then result := nil else result := FParentNode.GetNextChild(Self); end;
{ GetPrev возвращает предыдущий узел по ходу рекурсивного обхода дерева } function TEctoTreeNode.GetPrev: TEctoTreeNode; var Node: TEctoTreeNode; begin result := nil; if IsRoot then exit; result := GetPrevSibling(); // получаем предыдущий сестринский узел if result=nil then result := FParentNode // если его нет, значит наш узел первый, значит предыдущим будет его родитель else begin // а если есть... Node := result.LastDescendant; // получаем последнего потомка if Node<>nil then result := Node; // если такой существует (если вообще есть потомки) то он и будет предыдущим. Если же не существует, то result остается со значением полученным в строке result := GetPrevSibling(); end
end;
{ функция GetPrevChild возвращает предыдущий дочерний узел отсчитывая от заданного дочернего узла. Если заданный узел является первым дочерним узлом, функция возвращает nil } function TEctoTreeNode.GetPrevChild(TargetChildNode: TEctoTreeNode): TEctoTreeNode; var PrevChildIndex:integer; begin result := nil; PrevChildIndex := Children.IndexOf(TargetChildNode)-1; if PrevChildIndex>-1 then result := TEctoTreeNode(Children[PrevChildIndex]); end;
function TEctoTreeNode.GetPrevSibling: TEctoTreeNode; begin if IsRoot then result := nil else result := FParentNode.GetPrevChild(Self); end;
{ функция IsParentOf возвращает true если узел является предком заданного в независимости от их уровня } function TEctoTreeNode.IsParentOf(Node: TEctoTreeNode): boolean; var TempNode : TEctoTreeNode; begin result := false; TempNode := Node.FParentNode;
while TempNode<>nil do begin if TempNode = self then begin result := true; exit; end; TempNode := TempNode.FParentNode; end; end;
function TEctoTreeNode.IsRoot: boolean; begin result := (Self=ParentTree.Root); end;
{ процедура MoveDown перемещает узел вниз. Перемещение возможно только в пределах сестринских узлов, если узел является последним в списке детей текущего родителя, то перемещение невозможно } procedure TEctoTreeNode.MoveDown; var Temp: Pointer; ChildIndex: integer; begin if IsRoot then exit; if NextSibling<>nil then begin ChildIndex := Index; // временная переменная ChildIndex нужна т.к. Index - расчетное свойство, незачем лишние вызовы. Кроме того после первого оператора индекс теряется Temp := ParentNode.Children[ChildIndex]; ParentNode.Children[ChildIndex] := ParentNode.Children[ChildIndex+1]; ParentNode.Children[ChildIndex+1] := Temp; end; end;
{ процедура MoveLeft перемещает узел влево. Перемещение идет по принципу: новым родителем становится родитель родителя, а узел вставляется в список дочерних узлов родителя родителя таким образом, чтобы оказаться сразу после текущего родителя (текущий родитель после перемещения становится предыдущим сестринским узлом) } procedure TEctoTreeNode.MoveLeft; begin if (ParentNode.IsRoot) or (IsRoot) then exit; ParentNode.ParentNode.Children.Insert(ParentNode.Index+1,self); ParentNode.Children.Delete(ParentNode.Children.IndexOf(self)); FParentNode := ParentNode.ParentNode; // FParentNode используем вместо ParentNode потому что нам не нужен вызов всей процедуры присваивания родителя, мы всю работу делаем здесь сами и она специфична. end;
{ процедура MoveRight перемещает узел вправо. Перемещение идет по принципу: новым родителем становится предыдущий сестринский узел. Если предыдущего сестринского узла нет, перемещение считается невозможным } procedure TEctoTreeNode.MoveRight; begin if (IsRoot) or (PrevSibling=nil) then exit; // Если нет сестринского узла перед этим, то невозможно движение вправо ParentNode := PrevSibling; // Здесь вызов процедуры присваивания родителя. end;
{ процедура MoveUp перемещает узел вверх. Перемещение идет по принципу: если у узла есть сестринские узлы выше него, то узел просто встает выше предыдущего сестринского узла. Если же сестринских узлов выше нет (узел первый дочерний у родителя), то узел становится выше родительского, т.е. в конец дочерних узлов предыдущего сестринского узла родителя. } procedure TEctoTreeNode.MoveUp; var Temp: Pointer; ChildIndex: integer; begin if IsRoot then exit; if PrevSibling<>nil then begin ChildIndex := Index; // временная переменная ChildIndex нужна т.к. Index - расчетное свойство, незачем лишние вызовы. Кроме того после первого оператора индекс теряется Temp := ParentNode.Children[ChildIndex]; ParentNode.Children[ChildIndex] := ParentNode.Children[ChildIndex-1]; ParentNode.Children[ChildIndex-1] := Temp; end else begin if not ParentNode.IsRoot then begin ParentNode := ParentNode.ParentNode; // Это присваивание автоматически добавит узел в конец, последним дочерним. MoveUp; end; end; end;
{ установка нового родителя функцией SetParent фактически означает перенос ветви дерева в другую ветвь } procedure TEctoTreeNode.SetParent(NewParentNode: TEctoTreeNode); begin if (NewParentNode=nil) or (NewParentNode=self) then exit; ParentNode.Children.Delete(ParentNode.Children.IndexOf(self)); NewParentNode.Children.Add(self); self.FParentNode := NewParentNode; end;
procedure TEctoTreeNode.Sort(Compare: TListSortCompare; SortSubtrees: boolean); var i,j,CompareResult: integer; Temp : Pointer; begin j:=0; while j<Children.Count do begin
i:=Children.Count-1; while i>j do begin if i>j then begin CompareResult := Compare(Children[i],Children[i-1]); if CompareResult>0 then begin Temp := Children[i-1]; Children[i-1] := Children[i]; Children[i] := Temp; end; end; dec(i); end;
if SortSubtrees then TEctoTreeNode(Children[j]).Sort(Compare,true);
inc(j); end; end;
end. |
Автор: Мыш
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!