I've created a Node
class which contains two important properties:
public Node Parent { get; private set; }
private List<Node> Children { get; set;}
As the name suggests, the Parent
object holds information about the ancestor of a certain node, if the parent is the root of the tree, then the parent is set to null
. And the Children
collection stores all the descendant nodes.
The methods responsible for the searching are:
GetChildren()
GetChildrenRecursive()
All of them are described on the code documentation. But I am specially concerned about the performance and reliability of the searching algorithm and the overall implementation of the tree structure. I'd like to hear some opinions on how I could possibly improve the code quality or any reading material about searching algorithms on trees.
- Node.cs
/// <summary> /// Represents a tree-like structure /// </summary> public class Node { /// <summary> /// The ancestor (parent) of this node. Null if the current node is the root of the tree. /// </summary> public Node Parent { get; private set; } /// <summary> /// The descendats (children) of this node. /// </summary> private List<Node> Children { get; set; } /// <summary> /// Checks wheter the current node is the root of the tree. /// </summary> public bool IsRoot { get { return Parent != null; } } /// <summary> /// Checks wheter the current node has any children. /// </summary> public bool HasChildren { get { return Count > 0; } } /// <summary> /// The current node's children count. /// </summary> public int Count { get { return Children?.Count ?? 0; } } /// <summary> /// The object stored in the current node. /// </summary> public object Value { get; set; } /// <summary> /// Creates a new instance of the <see cref="Node"/> class with an empty object. /// </summary> /// <param name="value">The value that will be held by this node</param> public Node() { Value = new object(); Children = new List<Node>(); } /// <summary> /// Creates a new instance of the <see cref="Node"/> class with a set value. /// </summary> /// <param name="value">The value that will be held by this node</param> public Node(object value) { Value = value; Children = new List<Node>(); } /// <summary> /// Returns a copy of all values contained in this <see cref="Node"/>. /// <para> /// Useful for avoiding interferences between instances of the <see cref="Node"/> class. /// </para> /// </summary> /// <returns>A <see cref="Node"/> with the property values of this node</returns> public Node DeepCopy() { var other = (Node)MemberwiseClone(); other.Children = new List<Node>(collection: Children); other.Parent = Parent?.DeepCopy(); other.Value = new Node(value: Value); return other; } /// <summary> /// Adds a child to this <see cref="Node"/>. /// </summary> /// <param name="node">The node to be added</param> public void AddChild(Node node) { if (node != this && node.Parent == null) { node.Parent = this; Children.Add(node); } } /// <summary> /// Removes a child from this <see cref="Node"/>. /// </summary> /// <param name="node">The node to be removed</param> public void RemoveChild(Node node) { if (node != this && Children.Contains(node)) { Children.Remove(node); } } /// <summary> /// Performs a superficial search, returning the children on the first level. /// </summary> /// <returns>An <see cref="IEnumerable{Node}"/>containing the search result</returns> public IEnumerable<Node> GetChildren() { return Children.AsEnumerable(); } /// <summary> /// Performs a recursive search, returning all the children on all levels /// </summary> /// <returns>An <see cref="IEnumerable{Node}"/>containing the search result</returns> public IEnumerable<Node> GetChildrenRecursive() { var root = DeepCopy(); // No descendants have children. No recursion neeeded. if (root.Children.All(x => x.Children.Count == 0)) { return GetChildren(); } // Some (or all) descendants have children. Use recursion else { var allChildren = new List<Node>(); var searchQueue = new Queue<Node>(); // Adds the first generation of children into the queue GetChildren().ToList() .ForEach((x) => searchQueue.Enqueue(x)); // Loops until the queue is empty while (searchQueue.Any()) { // Adds the first children in the queue to the final collection allChildren.Add(searchQueue.Peek()); // Checks if the first children in the queue has descendants if (searchQueue.Peek().HasChildren) { // Adds the descendants of the children being searched on the queue searchQueue.Peek().Children .ForEach((x) => searchQueue.Enqueue(x)); } // Removes the first node on the queue, since it has been searched already. searchQueue.Dequeue(); } return allChildren; } } /// <summary> /// Override for the <code><see cref="object"/>.ToString()</code> method /// </summary> /// <returns>The string representation of this node's value</returns> public override string ToString() { return $"{Value?.ToString()}"; } }
Also, I'm including some tests I've made, all of them are passing as of now.
- NodeTest.cs
[TestClass] public class NodeTest { [TestMethod] public void Node_DeepCopy_CopySuccessful() { // Arrange var root = new Node(null); var node1 = new Node(null); var node2 = new Node(null); var copyNode = new Node(null); // Act root.AddChild(node1); root.AddChild(node2); copyNode = root.DeepCopy(); var actual = copyNode.HasChildren; // Assert Assert.AreEqual(true, actual); } [TestMethod] public void Node_DeepCopy_CopyIsIndependent() { // Arrange var root = new Node(null); var node1 = new Node(null); var node2 = new Node(null); var copyNode = new Node(null); // Act root.AddChild(node1); root.AddChild(node2); copyNode = root.DeepCopy(); root.AddChild(new Node(null)); var actual = root.Count != copyNode.Count; // Assert Assert.AreEqual(true, actual); } [TestMethod] public void Node_Search_ReturnsAllElements() { // Arrange const int EXPECTED_CHILDREN_COUNT = 3; var root = new Node(null); var root_child1 = new Node(null); var root_child2 = new Node(null); var root_child3 = new Node(null); // Act root.AddChild(root_child1); root.AddChild(root_child2); root.AddChild(root_child3); int actual = root.Count; // Assert Assert.AreEqual(EXPECTED_CHILDREN_COUNT, actual); } [TestMethod] public void Node_RecursiveSearch_ReturnsAllElements() { // Arrange const int EXPECTED_CHILDREN_COUNT = 9; var root = new Node("Root node"); var rc1 = new Node("[Gen 1] 1st child of: root"); var rc2 = new Node("[Gen 1] 2nd child of: root"); var rc3 = new Node("[Gen 1] 3rd child of: root"); var rc2_1 = new Node("[Gen 2] 1st child of: root's 2nd child"); var rc2_2 = new Node("[Gen 2] 2nd child of: root's 2nd child"); var rc3_1 = new Node("[Gen 2] 1st child of: root's 3rd child"); var rc2_1_1 = new Node("[Gen 3] 1st child of: root's 2nd child's 1st child"); var rc3_1_1 = new Node("[Gen 3] 1st child of: root's 3rd child's 1st child"); var rc3_1_1_1 = new Node("[Gen 4] 1st child of: root's 3rd child's 1st child's 1st child"); // Act rc2_1.AddChild(rc2_1_1); rc2.AddChild(rc2_1); rc2.AddChild(rc2_2); rc3_1_1.AddChild(rc3_1_1_1); rc3_1.AddChild(rc3_1_1); rc3.AddChild(rc3_1); root.AddChild(rc1); root.AddChild(rc2); root.AddChild(rc3); int actual = new List<Node>(root.GetChildrenRecursive()).Count; // Assert Assert.AreEqual(EXPECTED_CHILDREN_COUNT, actual); } }