383 lines
9.9 KiB
C#
383 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using JD.Kernel.StaticHelpers;
|
|
|
|
namespace JD.Collections
|
|
{
|
|
/// <summary>
|
|
/// TreeNode
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public class TreeNode<T> : IEnumerable<TreeNode<T>>
|
|
{
|
|
#region Variables
|
|
|
|
private T m_data;
|
|
private TreeNode<T> m_parent;
|
|
private TreeNode<T> m_child;
|
|
private TreeNode<T> m_sibling;
|
|
private TreeNode<T> m_lastChild;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Default constructor.
|
|
/// </summary>
|
|
public TreeNode()
|
|
{
|
|
m_data = default(T);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alternative constructor.
|
|
/// </summary>
|
|
/// <param name="data">Node's data.</param>
|
|
public TreeNode(T data)
|
|
{
|
|
m_data = data;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Is root.
|
|
/// </summary>
|
|
public bool IsRoot
|
|
{
|
|
get
|
|
{
|
|
return m_parent == null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is leaf.
|
|
/// </summary>
|
|
public bool IsLeaf
|
|
{
|
|
get
|
|
{
|
|
return m_child == null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is isolated.
|
|
/// </summary>
|
|
public bool IsIsolated
|
|
{
|
|
get
|
|
{
|
|
return IsRoot && IsLeaf;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Data.
|
|
/// </summary>
|
|
public T Data
|
|
{
|
|
get
|
|
{
|
|
return m_data;
|
|
}
|
|
set
|
|
{
|
|
m_data = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parent.
|
|
/// </summary>
|
|
public TreeNode<T> Parent
|
|
{
|
|
get
|
|
{
|
|
return m_parent;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Children.
|
|
/// </summary>
|
|
public IEnumerable<TreeNode<T>> Children
|
|
{
|
|
get
|
|
{
|
|
TreeNode<T> child = m_child;
|
|
while (child != null)
|
|
{
|
|
yield return child;
|
|
child = child.m_sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate directly through children data.
|
|
/// </summary>
|
|
public IEnumerable<T> ChildrenData
|
|
{
|
|
get
|
|
{
|
|
foreach (TreeNode<T> child in Children)
|
|
{
|
|
yield return child.Data;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Descendants.
|
|
/// </summary>
|
|
public IEnumerable<TreeNode<T>> Descendants
|
|
{
|
|
get
|
|
{
|
|
foreach (TreeNode<T> child in Children)
|
|
{
|
|
yield return child;
|
|
foreach (TreeNode<T> descendant in child.Descendants)
|
|
{
|
|
yield return descendant;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<TreeNode<T>> AllNodes
|
|
{
|
|
get
|
|
{
|
|
yield return this;
|
|
|
|
foreach (TreeNode<T> child in Descendants)
|
|
{
|
|
yield return child;
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<T> AllNodesData
|
|
{
|
|
get
|
|
{
|
|
yield return m_data;
|
|
|
|
foreach (TreeNode<T> child in Descendants)
|
|
{
|
|
yield return child.Data;
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Adds a new node with data as value.
|
|
/// </summary>
|
|
/// <param name="data">Data of the new child.</param>
|
|
/// <returns>The created node.</returns>
|
|
public TreeNode<T> Add(T data)
|
|
{
|
|
TreeNode<T> newChild = new TreeNode<T>(data);
|
|
AttachChild(newChild);
|
|
return newChild;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attach <paramref name="newChild"/> as a child.
|
|
/// </summary>
|
|
/// <remarks>The TreeNode <paramref name="newChild"/> will be detach from its current parent if it's not a root.</remarks>
|
|
/// <param name="newChild"></param>
|
|
public void AttachChild(TreeNode<T> newChild)
|
|
{
|
|
Throw.IfNull(newChild, "newChild");
|
|
Throw.InvalidOperationIf(newChild == this, "Trying to attach a node to itself.");
|
|
Throw.InvalidOperationIf(IsDescendantOf(newChild), "Try to attach a node to one of its descendants.");
|
|
|
|
if (newChild.Parent != null)
|
|
{
|
|
newChild.Detach();
|
|
}
|
|
|
|
if (m_child == null)
|
|
{
|
|
m_child = newChild;
|
|
}
|
|
else
|
|
{
|
|
m_lastChild.m_sibling = newChild;
|
|
}
|
|
|
|
newChild.m_parent = this;
|
|
m_lastChild = newChild;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detach from parent.
|
|
/// </summary>
|
|
public void Detach()
|
|
{
|
|
Throw.InvalidOperationIf(IsRoot, "Unable to detach a root node.");
|
|
|
|
if (m_parent.m_child == this)
|
|
{
|
|
//Parent's first child is us:
|
|
//Set the parent's new child as our sibling.
|
|
m_parent.m_child = m_sibling;
|
|
|
|
if (m_parent.m_lastChild == this)
|
|
{
|
|
m_parent.m_lastChild = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Parent's first child is not us:
|
|
//Iterate in the children of the parent until a child's sibling is us
|
|
TreeNode<T> childIter = m_parent.m_child;
|
|
while (childIter != null && childIter.m_sibling != this)
|
|
{
|
|
childIter = childIter.m_sibling;
|
|
}
|
|
|
|
System.Diagnostics.Debug.Assert(childIter != null, "Unable to find node in parent's child.");
|
|
if (m_parent.m_lastChild == this)
|
|
{
|
|
m_parent.m_lastChild = childIter;
|
|
}
|
|
|
|
childIter.m_sibling = m_sibling;
|
|
}
|
|
|
|
m_parent = null;
|
|
m_sibling = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is parent of <paramref name="node"/>.
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
/// <returns></returns>
|
|
public bool IsParentOf(TreeNode<T> node)
|
|
{
|
|
Throw.IfNull(node, "node");
|
|
return node.Parent == this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is ancestor of <paramref name="node"/>.
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
/// <returns></returns>
|
|
public bool IsAncestorOf(TreeNode<T> node)
|
|
{
|
|
Throw.IfNull(node, "node");
|
|
return node.IsDescendantOf(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is decendant of <paramref name="node"/>.
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
/// <returns></returns>
|
|
public bool IsDescendantOf(TreeNode<T> node)
|
|
{
|
|
Throw.IfNull(node, "node");
|
|
TreeNode<T> parent = m_parent;
|
|
while(parent != null && parent != node)
|
|
{
|
|
parent = parent.Parent;
|
|
}
|
|
|
|
return parent != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform <paramref name="action"/> for all children.
|
|
/// </summary>
|
|
/// <param name="action"></param>
|
|
public void ForEachChildren(Action<TreeNode<T>> action)
|
|
{
|
|
Throw.IfNull(action, "action");
|
|
|
|
TreeNode<T> child = m_child;
|
|
while(child != null)
|
|
{
|
|
//Cache the next child in case the child pointer gets
|
|
//invalidated in the action. This happens if, for example, you
|
|
//delete an item in the sibling chain while iterating. In this
|
|
//case, the pointer to m_sibling might be deleted, resulting in
|
|
//loosing items.
|
|
TreeNode<T> nextChild = child.m_sibling;
|
|
action(child);
|
|
child = nextChild;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform <paramref name="action"/> for all ancestor.
|
|
/// </summary>
|
|
/// <param name="action"></param>
|
|
public void ForEachAncestor(Action<TreeNode<T>> action)
|
|
{
|
|
Throw.IfNull(action, "action");
|
|
|
|
TreeNode<T> parent = m_parent;
|
|
while(parent != null)
|
|
{
|
|
//Cache the next parent. See reason in ForEachChildren
|
|
TreeNode<T> nextParent = parent.Parent;
|
|
action(parent);
|
|
parent = nextParent;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform <paramref name="action"/> for all descendant.
|
|
/// </summary>
|
|
/// <remarks>Action application is done before calling the child.
|
|
/// DO NOT MODIFY THE HIERARCHY STRUCTURE FROM THE GIVEN ACTION.</remarks>
|
|
/// <param name="action"></param>
|
|
public void ForEachDescendant(Action<TreeNode<T>> action)
|
|
{
|
|
Throw.IfNull(action, "action");
|
|
|
|
TreeNode<T> child = m_child;
|
|
while(child != null)
|
|
{
|
|
action(child);
|
|
child.ForEachDescendant(action);
|
|
child = child.m_sibling;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable<T> Members
|
|
|
|
public IEnumerator<TreeNode<T>> GetEnumerator()
|
|
{
|
|
return AllNodes.GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable Members
|
|
|
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
|
{
|
|
return AllNodes.GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|