using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace GB5Shared.Tree { public interface ITreeNode where T : class { /// /// A unique identifier for the node. /// int Id { get; } /// /// A unique identifier for the node. /// string Code { get; } /// /// The parent of this node, or null if it is the root of the tree. /// T Parent { get; set; } /// /// The children of this node, or an empty list if this is a leaf. /// IList children { get; set; } } /// /// A helper class for objects which implement , providing /// methods to convert flat lists to and from hierarchical trees, iterators, and /// other utility methods. /// public static class TreeHelper { #region Tree structure methods /// /// Converts an array of ITreeNode objects into a forest of trees. The returned list will /// contain only the root nodes, with each root having a populated Children /// property. /// /// /// An array of list of node objects, where the Parent /// property of each node is either null for root nodes, or an instantiated object with its /// Id property set. /// public static IList ConvertToTree(this IEnumerable flatNodeList) where T : class, ITreeNode { // first, put every TreeNode into a dictionary so that we can easily find tree nodes later. Dictionary dictionary = new Dictionary(); foreach (T node in flatNodeList) { // dictionary.Add(node.Id, node); if (!dictionary.ContainsKey(node.Id)) { dictionary.Add(node.Id, node); } // while we're looping, it's a good time to create the Children list node.children = new List(); } // Now, go through each TreeNode. If Parent is null, then it is a root node of a tree, // so add it to the 'rootNodes' List, which is what will be returned from this method. // If Parent is not null, then find the parent from the dictionary, and set that to be it's Parent. List rootNodes = new List(); int i = -1; foreach (T node in flatNodeList) { if (node.Parent == null) { // this is a root node; add it to the rootNodes list. rootNodes.Add(node); } else { // this is not a root node; add it as a child of its parent. if (!dictionary.ContainsKey(node.Parent.Id)) { // In this case, this node's parent is not in the flatNodeList. // By continuing, we are just ignoring this node (it won't be // returned in the tree). Another option would be to throw an // exception here. continue; } // make the parent reference for this node a reference to a fully populated parent. node.Parent = dictionary[node.Parent.Id]; // add this node to the child list of its parent. node.Parent.children.Add(node); //if (node.Parent.children.Count == 1) //{ // i = 0; //} //else //{ // i = i + 1; //} i = node.Parent.children.Count - 1; node.Parent.children[i].Parent = null; } } return rootNodes; } /// /// Converts a heirachacle Array of Tree Nodes into a flat array of nodes. The order /// of the returned nodes is the same as a depth-first traversal of each tree. /// /// The relationships between Parent/Children are retained. public static List ConvertToFlatArray(this IEnumerable trees) where T : class, ITreeNode { List treeNodeList = new List(); foreach (T rootNode in trees) { foreach (T node in DepthFirstTraversal(rootNode)) { treeNodeList.Add(node); } } return treeNodeList; } #endregion #region Search methods /// Finds the TreeNode with the given Id in the given tree by searching the descendents. /// Returns null if the node cannot be found. public static T FindDescendant(this T searchRoot, int id) where T : class, ITreeNode { EnsureTreePopulated(searchRoot, "searchRoot"); foreach (T child in DepthFirstTraversal(searchRoot)) { if (child.Id == id) { return child; } } return null; } /// Finds the TreeNode with the given id from the given forest of trees. /// Returns null if the node cannot be found. public static T FindTreeNode(this IEnumerable trees, int id) where T : class, ITreeNode { foreach (T rootNode in trees) { if (rootNode.Id == id) { return rootNode; } T descendant = FindDescendant(rootNode, id); if (descendant != null) { return descendant; } } return null; } #endregion #region Useful tree properties /// /// Checks whether there is a loop from the current node up the tree back to the current node. /// It is recommended that this is checked to be false before saving the node to your data store. /// /// /// The most simple example of a hierarchy loop is were there are 2 nodes, "A" and "B", and "A" /// is "B"'s parent, and "B" is "A"'s parent. This is not allowed, and should not be saved. , /// public static bool HasHeirachyLoop(this T node) where T : class, ITreeNode { EnsureTreePopulated(node, "node"); T tempParent = node.Parent; while (tempParent != null) { if (tempParent.Id == node.Id) { return true; } tempParent = tempParent.Parent; } return false; } /// Returns the root node of the tree that the given TreeNode belongs in public static T GetRootNode(this T node) where T : class, ITreeNode { EnsureTreePopulated(node, "node"); T cur = node; while (cur.Parent != null) { cur = cur.Parent; } return cur; } /// /// Gets the depth of a node, e.g. a root node has depth 0, its children have depth 1, etc. /// public static int GetDepth(this T node) where T : class, ITreeNode { EnsureTreePopulated(node, "node"); int depth = 0; while (node.Parent != null) { ++depth; node = node.Parent; } return depth; } /// /// Gets the type of node that the specified node is. /// public static NodeType GetNodeType(this T node) where T : class, ITreeNode { EnsureTreePopulated(node, "node"); if (node.Parent == null) { return NodeType.Root; } else if (node.children.Count == 0) { return NodeType.Leaf; } return NodeType.Internal; } #endregion #region Iterators /// /// Returns an Iterator which starts at the given node, and climbs up the tree to /// the root node. /// /// The node to start iterating from. This will be the first node returned by the iterator. public static IEnumerable ClimbToRoot(this T startNode) where T : class, ITreeNode { EnsureTreePopulated(startNode, "startNode"); T current = startNode; while (current != null) { yield return current; current = current.Parent; } } /// /// Returns an Iterator which starts at the root node, and goes down the tree to /// the given node node. /// /// The node to start iterating from. This will be the first node returned by the iterator. public static List FromRootToNode(this T node) where T : class, ITreeNode { EnsureTreePopulated(node, "node"); List nodeToRootList = new List(); foreach (T n in ClimbToRoot(node)) { nodeToRootList.Add(n); } nodeToRootList.Reverse(); return nodeToRootList; } /// /// Returns an Iterator which starts at the given node, and traverses the tree in /// a depth-first search manner. /// /// The node to start iterating from. This will be the first node returned by the iterator. public static IEnumerable DepthFirstTraversal(this T startNode) where T : class, ITreeNode { EnsureTreePopulated(startNode, "node"); yield return startNode; foreach (T child in startNode.children) { foreach (T grandChild in DepthFirstTraversal(child)) { yield return grandChild; } } } /// /// Returns an Iterator which traverses a forest of trees in a depth-first manner. /// /// The forest of trees to traverse. public static IEnumerable DepthFirstTraversalOfList(this IEnumerable trees) where T : class, ITreeNode { foreach (T rootNode in trees) { foreach (T node in DepthFirstTraversal(rootNode)) { yield return node; } } } /// /// Gets the siblings of the given node. Note that the given node is included in the /// returned list. Throws an if this is a root node. /// /// The node whose siblings are to be returned. /// If false, then the supplied node will not be returned in the sibling list. public static IEnumerable Siblings(this T node, bool includeGivenNode) where T : class, ITreeNode { EnsureTreePopulated(node, "node"); if (GetNodeType(node) == NodeType.Root) { if (includeGivenNode) { yield return node; } yield break; } foreach (T sibling in node.Parent.children) { if (!includeGivenNode && sibling.Id == node.Id) { // current node is supplied node; don't return it unless it was asked for. continue; } yield return sibling; } } /// /// Traverses the tree in a breadth-first fashion. /// /// The node to start at. /// If true, the given node will be returned; if false, traversal starts at the node's children. public static IEnumerable BreadthFirstTraversal(this T node, bool returnRootNode) where T : class, ITreeNode { EnsureTreePopulated(node, "node"); if (returnRootNode) { yield return node; } foreach (T child in node.children) { yield return child; } foreach (T child in node.children) { foreach (T grandChild in BreadthFirstTraversal(child, false)) { yield return grandChild; } } } #endregion #region Private methods [System.Diagnostics.Conditional("DEBUG")] private static void EnsureTreePopulated(T node, string parameterName) where T : class, ITreeNode { if (node == null) { throw new ArgumentNullException(parameterName, "The given node cannot be null."); } if (node.children == null) { throw new ArgumentException("The children of " + parameterName + " is null. Have you populated the tree fully by calling TreeHelper.ConvertToForest(IEnumerable flatNodeList)?", parameterName); } } #endregion } /// /// A type of tree node. /// public enum NodeType { /// /// A node which is at the root of the tree, i.e. it has no parents. /// Root, /// /// A node which has parent and children. /// Internal, /// /// A node with no children. /// Leaf } }