Recursive search on Node Tree with Linq and QueuePrinting a Binary Tree top-down (column wise)In-memory B-Tree in C++11Composable custom control that supports binding and property inheritanceFind paths whose sum equals a target value in a binary treeRecursive statement with linq2D Bin Packing Algorithm ImplementationGiven a binary tree, find the maximum path sumJava n-ary Tree class with custom made methods and nested Node classExecute promise tree in order of declarationGet all node descendants in a tree
Monday's Blocking Donimoes Problem
Storyboarding Approaches for the Non-Artistic
Is it better to deliver many low-value stories or few high-value stories?
Why is a PhD thesis typically 150 pages?
Can a warlock shoot multiple beams from the Eldritch Blast cantrip with only a single free hand?
Strange LED behavior
How can I disable a reserved profile?
Did Don Young threaten John Boehner with a 10 inch blade to the throat?
Is there an English word to describe when a sound "protrudes"?
Counterexample finite intersection property
Found old paper shares of Motorola Inc that has since been broken up
What do Unicorns want?
Is there any direct train from LHR Airport to Newcastle Gateshead?
What kind of curve (or model) should I fit to my percentage data?
Can the caster of Time Stop still use their bonus action or reaction?
Why are Oscar, India, and X-Ray (O, I, and X) not used as taxiway identifiers?
Is there a guide to help me transition from PSTricks to TikZ?
Create Circle with Inner Radius
What kind of vegetable has pink and white concentric rings?
How should I handle a question regarding my regrets during an interview?
What does a Nintendo Game Boy do when turned on without a game cartridge inserted?
Source for "everyone has a specific area of Torah that they're naturally drawn to"
How did pilots avoid thunderstorms and related weather before “reliable” airborne weather radar was introduced on airliners?
Is it better to merge "often" or only after completion do a big merge of feature branches?
Recursive search on Node Tree with Linq and Queue
Printing a Binary Tree top-down (column wise)In-memory B-Tree in C++11Composable custom control that supports binding and property inheritanceFind paths whose sum equals a target value in a binary treeRecursive statement with linq2D Bin Packing Algorithm ImplementationGiven a binary tree, find the maximum path sumJava n-ary Tree class with custom made methods and nested Node classExecute promise tree in order of declarationGet all node descendants in a tree
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
$begingroup$
This code is now maintained on GitHub
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="IEnumerableNode"/>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="IEnumerableNode"/>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 test 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);
c# recursion unit-testing tree
New contributor
$endgroup$
add a comment |
$begingroup$
This code is now maintained on GitHub
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="IEnumerableNode"/>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="IEnumerableNode"/>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 test 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);
c# recursion unit-testing tree
New contributor
$endgroup$
$begingroup$
@dfhwze I'm sure there is a better/faster alternative to my approach to this.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze Thinking now, If I don't find anything related to it in the MS assemblies, I'll just remove the tag
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze removed it
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
meanwhile, I answered your question :) Btw, good to see unit tests in a question.
$endgroup$
– dfhwze
7 hours ago
add a comment |
$begingroup$
This code is now maintained on GitHub
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="IEnumerableNode"/>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="IEnumerableNode"/>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 test 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);
c# recursion unit-testing tree
New contributor
$endgroup$
This code is now maintained on GitHub
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="IEnumerableNode"/>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="IEnumerableNode"/>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 test 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);
c# recursion unit-testing tree
c# recursion unit-testing tree
New contributor
New contributor
edited 4 hours ago
Stacklysm
New contributor
asked 8 hours ago
StacklysmStacklysm
236 bronze badges
236 bronze badges
New contributor
New contributor
$begingroup$
@dfhwze I'm sure there is a better/faster alternative to my approach to this.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze Thinking now, If I don't find anything related to it in the MS assemblies, I'll just remove the tag
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze removed it
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
meanwhile, I answered your question :) Btw, good to see unit tests in a question.
$endgroup$
– dfhwze
7 hours ago
add a comment |
$begingroup$
@dfhwze I'm sure there is a better/faster alternative to my approach to this.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze Thinking now, If I don't find anything related to it in the MS assemblies, I'll just remove the tag
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze removed it
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
meanwhile, I answered your question :) Btw, good to see unit tests in a question.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
@dfhwze I'm sure there is a better/faster alternative to my approach to this.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze I'm sure there is a better/faster alternative to my approach to this.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze Thinking now, If I don't find anything related to it in the MS assemblies, I'll just remove the tag
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze Thinking now, If I don't find anything related to it in the MS assemblies, I'll just remove the tag
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze removed it
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze removed it
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
meanwhile, I answered your question :) Btw, good to see unit tests in a question.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
meanwhile, I answered your question :) Btw, good to see unit tests in a question.
$endgroup$
– dfhwze
7 hours ago
add a comment |
2 Answers
2
active
oldest
votes
$begingroup$
Reading Material
.. any reading material about searching algorithms on trees
These are the most common tree walkers:
- Breadth-First Search
- Depth-First Search
Review
There is a bug with IsRoot. Also, why not provide a property Root get;
?
if the parent is the root of the tree, then the parent is set to null
public bool IsRoot get return Parent != null;
You should also take advantage of the sweet syntactic sugar of the language (for all your properties):
public bool IsRoot => Parent == null;
Since Children
is private and you always instantiate a list, there is no reason to use null-propagation here:
public int Count get return Children?.Count ?? 0;
public int Count => Children.Count;
AddChild
should throw exceptions on invalid input. You don't check for an invalid tree, what if the node
is a grand-parent of the the current instance? Perform similar checks for RemoveChild
.
public void AddChild(Node node)
node = node ?? throw new ArgumentNullException(nameof(node));
if (IsAncestorOrSelf(node)) // <- you should implement such method
throw new ArgumentException("The node can not be an ancestor or self");
if (IsDescendant(node)) // <- you should implement such method
throw new ArgumentException("The node can not be a descendant");
node.Parent = this;
Children.Add(node);
GetChildren
should return an immutable copy of the list containing the children.
public IEnumerable<Node> GetChildren()
return Children.ToArray();
I don't know why you would need DeepCopy
functionality.
GetChildrenRecursive
should be called GetDescendants
. I would implement it using recursion. This is implemented as depth-first (DFS).
public IEnumerable<Node> GetDescendants()
foreach (var child in Children)
yield return child;
foreach (var descendant in child.GetDescendants())
yield return descendant;
$endgroup$
$begingroup$
Thank you for taking your time and reviewing the code. Apparently I was paranoid that my first implementation of this method would mess up with the original object. I fixed the following: A) IsRoot code (now, if the parent is not set, then it's considered as the rootParent == null
; B) Removed the null check on Children; C) Search method now returns immutable copies of the descendants
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
I used recursion in GetDescendants because you have tagged your question as such. It is also possible without. I am sure someone will review that part.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
Ok. About the addition of new children, I'm doing the following check:return !node.Children.Contains(this) && node != this && !Children.Contains(node);
. If it's false, then ArgumentException is thrown.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Once this question has sufficient answers, you can always ask a follow-up question with the new code. I will be happy review that question as well. For now, I can say, this new check does not cover all edge cases :)
$endgroup$
– dfhwze
7 hours ago
1
$begingroup$
I was worried about changing the code (which isn't allowed here). As you said, once there are sufficient suggestions, I'll do a follow-up. And again, thanks for you time spent into this matter :)
$endgroup$
– Stacklysm
7 hours ago
add a comment |
$begingroup$
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;
You should be careful when using this, because it actually clones the entire tree (via Parent
and Children
). Besides that, I think MemberwiseClone
copies the Children
and Parent
recursively. So by creating a new list for the Children
and calling DeepCopy()
on the Parent
you actually get a mix of copies and existing Node
objects, that can lead to unexpected behavior if you change either the copy or the original later on. And the child instances (other
) will not be part of the parents Children
list in the copy.
Why does other.Value
become a Node(Value)
? - Value
is by the way also covered by MemberwiseClone
.
Consider if it is of any use and possibly skip it. I can't see any use of it?
public void RemoveChild(Node node)
if (node != this && Children.Contains(node))
Children.Remove(node);
It is safe to call Children.Remove(node)
even if node
is null
, so you can spare the Contains()
check. node != this
- I suppose this should be avoided in the Add
method - but why can't this
be removed if provided as node
? You could consider to return the bool
values returned from Children.Remove(node)
, to let the client known if the operation was succesful or not.
You could consider to make the Node
class generic:
public class Node<T>
public T Value get;
...
As of GetChildrenRecursive()
it seems to work, but looks rather complicated as a BFS algorithm to be. Remember that you have private access to the properties and fields of Node
instances, so you can for instance call Children
on any Node
not just this
. Below is a revised version, that is a little easier to follow:
public IEnumerable<Node> GetChildrenRecursive()
if (!HasChildren) yield break;
Queue<Node> queue = new Queue<Node>(this.Children);
while (queue.Count > 0)
var node = queue.Dequeue();
yield return node;
if (node.HasChildren)
foreach (Node child in node.Children)
queue.Enqueue(child);
It uses yield return
instead of creating a concrete List<Node>
object which is more in line with the return value IEnumerable<Node>
.
$endgroup$
1
$begingroup$
I think he meant GetChildrenRecursive as search function :) btw I reached my vote limit of today :(
$endgroup$
– dfhwze
6 hours ago
1
$begingroup$
@Henrik Hansen GetChildrenRecursive() is the method I'm talking about, if you are doing this because I got something mixed up (I can't blame you), then that went completely over my head. As of the DeepCopy(Node), I removed it, there was no use to it. And I'm planning to make this a generic type, I used theobject
type because gave more freedom over what can be inserted in the tree.
$endgroup$
– Stacklysm
5 hours ago
$begingroup$
And I will remove the null check when removing nodes
$endgroup$
– Stacklysm
5 hours ago
2
$begingroup$
Node<T> can always be used as Node<object> if you need to. So making it generic can't hurt your design.
$endgroup$
– dfhwze
5 hours ago
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Stacklysm is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f224691%2frecursive-search-on-node-tree-with-linq-and-queue%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Reading Material
.. any reading material about searching algorithms on trees
These are the most common tree walkers:
- Breadth-First Search
- Depth-First Search
Review
There is a bug with IsRoot. Also, why not provide a property Root get;
?
if the parent is the root of the tree, then the parent is set to null
public bool IsRoot get return Parent != null;
You should also take advantage of the sweet syntactic sugar of the language (for all your properties):
public bool IsRoot => Parent == null;
Since Children
is private and you always instantiate a list, there is no reason to use null-propagation here:
public int Count get return Children?.Count ?? 0;
public int Count => Children.Count;
AddChild
should throw exceptions on invalid input. You don't check for an invalid tree, what if the node
is a grand-parent of the the current instance? Perform similar checks for RemoveChild
.
public void AddChild(Node node)
node = node ?? throw new ArgumentNullException(nameof(node));
if (IsAncestorOrSelf(node)) // <- you should implement such method
throw new ArgumentException("The node can not be an ancestor or self");
if (IsDescendant(node)) // <- you should implement such method
throw new ArgumentException("The node can not be a descendant");
node.Parent = this;
Children.Add(node);
GetChildren
should return an immutable copy of the list containing the children.
public IEnumerable<Node> GetChildren()
return Children.ToArray();
I don't know why you would need DeepCopy
functionality.
GetChildrenRecursive
should be called GetDescendants
. I would implement it using recursion. This is implemented as depth-first (DFS).
public IEnumerable<Node> GetDescendants()
foreach (var child in Children)
yield return child;
foreach (var descendant in child.GetDescendants())
yield return descendant;
$endgroup$
$begingroup$
Thank you for taking your time and reviewing the code. Apparently I was paranoid that my first implementation of this method would mess up with the original object. I fixed the following: A) IsRoot code (now, if the parent is not set, then it's considered as the rootParent == null
; B) Removed the null check on Children; C) Search method now returns immutable copies of the descendants
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
I used recursion in GetDescendants because you have tagged your question as such. It is also possible without. I am sure someone will review that part.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
Ok. About the addition of new children, I'm doing the following check:return !node.Children.Contains(this) && node != this && !Children.Contains(node);
. If it's false, then ArgumentException is thrown.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Once this question has sufficient answers, you can always ask a follow-up question with the new code. I will be happy review that question as well. For now, I can say, this new check does not cover all edge cases :)
$endgroup$
– dfhwze
7 hours ago
1
$begingroup$
I was worried about changing the code (which isn't allowed here). As you said, once there are sufficient suggestions, I'll do a follow-up. And again, thanks for you time spent into this matter :)
$endgroup$
– Stacklysm
7 hours ago
add a comment |
$begingroup$
Reading Material
.. any reading material about searching algorithms on trees
These are the most common tree walkers:
- Breadth-First Search
- Depth-First Search
Review
There is a bug with IsRoot. Also, why not provide a property Root get;
?
if the parent is the root of the tree, then the parent is set to null
public bool IsRoot get return Parent != null;
You should also take advantage of the sweet syntactic sugar of the language (for all your properties):
public bool IsRoot => Parent == null;
Since Children
is private and you always instantiate a list, there is no reason to use null-propagation here:
public int Count get return Children?.Count ?? 0;
public int Count => Children.Count;
AddChild
should throw exceptions on invalid input. You don't check for an invalid tree, what if the node
is a grand-parent of the the current instance? Perform similar checks for RemoveChild
.
public void AddChild(Node node)
node = node ?? throw new ArgumentNullException(nameof(node));
if (IsAncestorOrSelf(node)) // <- you should implement such method
throw new ArgumentException("The node can not be an ancestor or self");
if (IsDescendant(node)) // <- you should implement such method
throw new ArgumentException("The node can not be a descendant");
node.Parent = this;
Children.Add(node);
GetChildren
should return an immutable copy of the list containing the children.
public IEnumerable<Node> GetChildren()
return Children.ToArray();
I don't know why you would need DeepCopy
functionality.
GetChildrenRecursive
should be called GetDescendants
. I would implement it using recursion. This is implemented as depth-first (DFS).
public IEnumerable<Node> GetDescendants()
foreach (var child in Children)
yield return child;
foreach (var descendant in child.GetDescendants())
yield return descendant;
$endgroup$
$begingroup$
Thank you for taking your time and reviewing the code. Apparently I was paranoid that my first implementation of this method would mess up with the original object. I fixed the following: A) IsRoot code (now, if the parent is not set, then it's considered as the rootParent == null
; B) Removed the null check on Children; C) Search method now returns immutable copies of the descendants
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
I used recursion in GetDescendants because you have tagged your question as such. It is also possible without. I am sure someone will review that part.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
Ok. About the addition of new children, I'm doing the following check:return !node.Children.Contains(this) && node != this && !Children.Contains(node);
. If it's false, then ArgumentException is thrown.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Once this question has sufficient answers, you can always ask a follow-up question with the new code. I will be happy review that question as well. For now, I can say, this new check does not cover all edge cases :)
$endgroup$
– dfhwze
7 hours ago
1
$begingroup$
I was worried about changing the code (which isn't allowed here). As you said, once there are sufficient suggestions, I'll do a follow-up. And again, thanks for you time spent into this matter :)
$endgroup$
– Stacklysm
7 hours ago
add a comment |
$begingroup$
Reading Material
.. any reading material about searching algorithms on trees
These are the most common tree walkers:
- Breadth-First Search
- Depth-First Search
Review
There is a bug with IsRoot. Also, why not provide a property Root get;
?
if the parent is the root of the tree, then the parent is set to null
public bool IsRoot get return Parent != null;
You should also take advantage of the sweet syntactic sugar of the language (for all your properties):
public bool IsRoot => Parent == null;
Since Children
is private and you always instantiate a list, there is no reason to use null-propagation here:
public int Count get return Children?.Count ?? 0;
public int Count => Children.Count;
AddChild
should throw exceptions on invalid input. You don't check for an invalid tree, what if the node
is a grand-parent of the the current instance? Perform similar checks for RemoveChild
.
public void AddChild(Node node)
node = node ?? throw new ArgumentNullException(nameof(node));
if (IsAncestorOrSelf(node)) // <- you should implement such method
throw new ArgumentException("The node can not be an ancestor or self");
if (IsDescendant(node)) // <- you should implement such method
throw new ArgumentException("The node can not be a descendant");
node.Parent = this;
Children.Add(node);
GetChildren
should return an immutable copy of the list containing the children.
public IEnumerable<Node> GetChildren()
return Children.ToArray();
I don't know why you would need DeepCopy
functionality.
GetChildrenRecursive
should be called GetDescendants
. I would implement it using recursion. This is implemented as depth-first (DFS).
public IEnumerable<Node> GetDescendants()
foreach (var child in Children)
yield return child;
foreach (var descendant in child.GetDescendants())
yield return descendant;
$endgroup$
Reading Material
.. any reading material about searching algorithms on trees
These are the most common tree walkers:
- Breadth-First Search
- Depth-First Search
Review
There is a bug with IsRoot. Also, why not provide a property Root get;
?
if the parent is the root of the tree, then the parent is set to null
public bool IsRoot get return Parent != null;
You should also take advantage of the sweet syntactic sugar of the language (for all your properties):
public bool IsRoot => Parent == null;
Since Children
is private and you always instantiate a list, there is no reason to use null-propagation here:
public int Count get return Children?.Count ?? 0;
public int Count => Children.Count;
AddChild
should throw exceptions on invalid input. You don't check for an invalid tree, what if the node
is a grand-parent of the the current instance? Perform similar checks for RemoveChild
.
public void AddChild(Node node)
node = node ?? throw new ArgumentNullException(nameof(node));
if (IsAncestorOrSelf(node)) // <- you should implement such method
throw new ArgumentException("The node can not be an ancestor or self");
if (IsDescendant(node)) // <- you should implement such method
throw new ArgumentException("The node can not be a descendant");
node.Parent = this;
Children.Add(node);
GetChildren
should return an immutable copy of the list containing the children.
public IEnumerable<Node> GetChildren()
return Children.ToArray();
I don't know why you would need DeepCopy
functionality.
GetChildrenRecursive
should be called GetDescendants
. I would implement it using recursion. This is implemented as depth-first (DFS).
public IEnumerable<Node> GetDescendants()
foreach (var child in Children)
yield return child;
foreach (var descendant in child.GetDescendants())
yield return descendant;
edited 6 hours ago
answered 7 hours ago
dfhwzedfhwze
5,5001 gold badge7 silver badges36 bronze badges
5,5001 gold badge7 silver badges36 bronze badges
$begingroup$
Thank you for taking your time and reviewing the code. Apparently I was paranoid that my first implementation of this method would mess up with the original object. I fixed the following: A) IsRoot code (now, if the parent is not set, then it's considered as the rootParent == null
; B) Removed the null check on Children; C) Search method now returns immutable copies of the descendants
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
I used recursion in GetDescendants because you have tagged your question as such. It is also possible without. I am sure someone will review that part.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
Ok. About the addition of new children, I'm doing the following check:return !node.Children.Contains(this) && node != this && !Children.Contains(node);
. If it's false, then ArgumentException is thrown.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Once this question has sufficient answers, you can always ask a follow-up question with the new code. I will be happy review that question as well. For now, I can say, this new check does not cover all edge cases :)
$endgroup$
– dfhwze
7 hours ago
1
$begingroup$
I was worried about changing the code (which isn't allowed here). As you said, once there are sufficient suggestions, I'll do a follow-up. And again, thanks for you time spent into this matter :)
$endgroup$
– Stacklysm
7 hours ago
add a comment |
$begingroup$
Thank you for taking your time and reviewing the code. Apparently I was paranoid that my first implementation of this method would mess up with the original object. I fixed the following: A) IsRoot code (now, if the parent is not set, then it's considered as the rootParent == null
; B) Removed the null check on Children; C) Search method now returns immutable copies of the descendants
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
I used recursion in GetDescendants because you have tagged your question as such. It is also possible without. I am sure someone will review that part.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
Ok. About the addition of new children, I'm doing the following check:return !node.Children.Contains(this) && node != this && !Children.Contains(node);
. If it's false, then ArgumentException is thrown.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Once this question has sufficient answers, you can always ask a follow-up question with the new code. I will be happy review that question as well. For now, I can say, this new check does not cover all edge cases :)
$endgroup$
– dfhwze
7 hours ago
1
$begingroup$
I was worried about changing the code (which isn't allowed here). As you said, once there are sufficient suggestions, I'll do a follow-up. And again, thanks for you time spent into this matter :)
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Thank you for taking your time and reviewing the code. Apparently I was paranoid that my first implementation of this method would mess up with the original object. I fixed the following: A) IsRoot code (now, if the parent is not set, then it's considered as the root
Parent == null
; B) Removed the null check on Children; C) Search method now returns immutable copies of the descendants$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Thank you for taking your time and reviewing the code. Apparently I was paranoid that my first implementation of this method would mess up with the original object. I fixed the following: A) IsRoot code (now, if the parent is not set, then it's considered as the root
Parent == null
; B) Removed the null check on Children; C) Search method now returns immutable copies of the descendants$endgroup$
– Stacklysm
7 hours ago
$begingroup$
I used recursion in GetDescendants because you have tagged your question as such. It is also possible without. I am sure someone will review that part.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
I used recursion in GetDescendants because you have tagged your question as such. It is also possible without. I am sure someone will review that part.
$endgroup$
– dfhwze
7 hours ago
$begingroup$
Ok. About the addition of new children, I'm doing the following check:
return !node.Children.Contains(this) && node != this && !Children.Contains(node);
. If it's false, then ArgumentException is thrown.$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Ok. About the addition of new children, I'm doing the following check:
return !node.Children.Contains(this) && node != this && !Children.Contains(node);
. If it's false, then ArgumentException is thrown.$endgroup$
– Stacklysm
7 hours ago
$begingroup$
Once this question has sufficient answers, you can always ask a follow-up question with the new code. I will be happy review that question as well. For now, I can say, this new check does not cover all edge cases :)
$endgroup$
– dfhwze
7 hours ago
$begingroup$
Once this question has sufficient answers, you can always ask a follow-up question with the new code. I will be happy review that question as well. For now, I can say, this new check does not cover all edge cases :)
$endgroup$
– dfhwze
7 hours ago
1
1
$begingroup$
I was worried about changing the code (which isn't allowed here). As you said, once there are sufficient suggestions, I'll do a follow-up. And again, thanks for you time spent into this matter :)
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
I was worried about changing the code (which isn't allowed here). As you said, once there are sufficient suggestions, I'll do a follow-up. And again, thanks for you time spent into this matter :)
$endgroup$
– Stacklysm
7 hours ago
add a comment |
$begingroup$
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;
You should be careful when using this, because it actually clones the entire tree (via Parent
and Children
). Besides that, I think MemberwiseClone
copies the Children
and Parent
recursively. So by creating a new list for the Children
and calling DeepCopy()
on the Parent
you actually get a mix of copies and existing Node
objects, that can lead to unexpected behavior if you change either the copy or the original later on. And the child instances (other
) will not be part of the parents Children
list in the copy.
Why does other.Value
become a Node(Value)
? - Value
is by the way also covered by MemberwiseClone
.
Consider if it is of any use and possibly skip it. I can't see any use of it?
public void RemoveChild(Node node)
if (node != this && Children.Contains(node))
Children.Remove(node);
It is safe to call Children.Remove(node)
even if node
is null
, so you can spare the Contains()
check. node != this
- I suppose this should be avoided in the Add
method - but why can't this
be removed if provided as node
? You could consider to return the bool
values returned from Children.Remove(node)
, to let the client known if the operation was succesful or not.
You could consider to make the Node
class generic:
public class Node<T>
public T Value get;
...
As of GetChildrenRecursive()
it seems to work, but looks rather complicated as a BFS algorithm to be. Remember that you have private access to the properties and fields of Node
instances, so you can for instance call Children
on any Node
not just this
. Below is a revised version, that is a little easier to follow:
public IEnumerable<Node> GetChildrenRecursive()
if (!HasChildren) yield break;
Queue<Node> queue = new Queue<Node>(this.Children);
while (queue.Count > 0)
var node = queue.Dequeue();
yield return node;
if (node.HasChildren)
foreach (Node child in node.Children)
queue.Enqueue(child);
It uses yield return
instead of creating a concrete List<Node>
object which is more in line with the return value IEnumerable<Node>
.
$endgroup$
1
$begingroup$
I think he meant GetChildrenRecursive as search function :) btw I reached my vote limit of today :(
$endgroup$
– dfhwze
6 hours ago
1
$begingroup$
@Henrik Hansen GetChildrenRecursive() is the method I'm talking about, if you are doing this because I got something mixed up (I can't blame you), then that went completely over my head. As of the DeepCopy(Node), I removed it, there was no use to it. And I'm planning to make this a generic type, I used theobject
type because gave more freedom over what can be inserted in the tree.
$endgroup$
– Stacklysm
5 hours ago
$begingroup$
And I will remove the null check when removing nodes
$endgroup$
– Stacklysm
5 hours ago
2
$begingroup$
Node<T> can always be used as Node<object> if you need to. So making it generic can't hurt your design.
$endgroup$
– dfhwze
5 hours ago
add a comment |
$begingroup$
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;
You should be careful when using this, because it actually clones the entire tree (via Parent
and Children
). Besides that, I think MemberwiseClone
copies the Children
and Parent
recursively. So by creating a new list for the Children
and calling DeepCopy()
on the Parent
you actually get a mix of copies and existing Node
objects, that can lead to unexpected behavior if you change either the copy or the original later on. And the child instances (other
) will not be part of the parents Children
list in the copy.
Why does other.Value
become a Node(Value)
? - Value
is by the way also covered by MemberwiseClone
.
Consider if it is of any use and possibly skip it. I can't see any use of it?
public void RemoveChild(Node node)
if (node != this && Children.Contains(node))
Children.Remove(node);
It is safe to call Children.Remove(node)
even if node
is null
, so you can spare the Contains()
check. node != this
- I suppose this should be avoided in the Add
method - but why can't this
be removed if provided as node
? You could consider to return the bool
values returned from Children.Remove(node)
, to let the client known if the operation was succesful or not.
You could consider to make the Node
class generic:
public class Node<T>
public T Value get;
...
As of GetChildrenRecursive()
it seems to work, but looks rather complicated as a BFS algorithm to be. Remember that you have private access to the properties and fields of Node
instances, so you can for instance call Children
on any Node
not just this
. Below is a revised version, that is a little easier to follow:
public IEnumerable<Node> GetChildrenRecursive()
if (!HasChildren) yield break;
Queue<Node> queue = new Queue<Node>(this.Children);
while (queue.Count > 0)
var node = queue.Dequeue();
yield return node;
if (node.HasChildren)
foreach (Node child in node.Children)
queue.Enqueue(child);
It uses yield return
instead of creating a concrete List<Node>
object which is more in line with the return value IEnumerable<Node>
.
$endgroup$
1
$begingroup$
I think he meant GetChildrenRecursive as search function :) btw I reached my vote limit of today :(
$endgroup$
– dfhwze
6 hours ago
1
$begingroup$
@Henrik Hansen GetChildrenRecursive() is the method I'm talking about, if you are doing this because I got something mixed up (I can't blame you), then that went completely over my head. As of the DeepCopy(Node), I removed it, there was no use to it. And I'm planning to make this a generic type, I used theobject
type because gave more freedom over what can be inserted in the tree.
$endgroup$
– Stacklysm
5 hours ago
$begingroup$
And I will remove the null check when removing nodes
$endgroup$
– Stacklysm
5 hours ago
2
$begingroup$
Node<T> can always be used as Node<object> if you need to. So making it generic can't hurt your design.
$endgroup$
– dfhwze
5 hours ago
add a comment |
$begingroup$
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;
You should be careful when using this, because it actually clones the entire tree (via Parent
and Children
). Besides that, I think MemberwiseClone
copies the Children
and Parent
recursively. So by creating a new list for the Children
and calling DeepCopy()
on the Parent
you actually get a mix of copies and existing Node
objects, that can lead to unexpected behavior if you change either the copy or the original later on. And the child instances (other
) will not be part of the parents Children
list in the copy.
Why does other.Value
become a Node(Value)
? - Value
is by the way also covered by MemberwiseClone
.
Consider if it is of any use and possibly skip it. I can't see any use of it?
public void RemoveChild(Node node)
if (node != this && Children.Contains(node))
Children.Remove(node);
It is safe to call Children.Remove(node)
even if node
is null
, so you can spare the Contains()
check. node != this
- I suppose this should be avoided in the Add
method - but why can't this
be removed if provided as node
? You could consider to return the bool
values returned from Children.Remove(node)
, to let the client known if the operation was succesful or not.
You could consider to make the Node
class generic:
public class Node<T>
public T Value get;
...
As of GetChildrenRecursive()
it seems to work, but looks rather complicated as a BFS algorithm to be. Remember that you have private access to the properties and fields of Node
instances, so you can for instance call Children
on any Node
not just this
. Below is a revised version, that is a little easier to follow:
public IEnumerable<Node> GetChildrenRecursive()
if (!HasChildren) yield break;
Queue<Node> queue = new Queue<Node>(this.Children);
while (queue.Count > 0)
var node = queue.Dequeue();
yield return node;
if (node.HasChildren)
foreach (Node child in node.Children)
queue.Enqueue(child);
It uses yield return
instead of creating a concrete List<Node>
object which is more in line with the return value IEnumerable<Node>
.
$endgroup$
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;
You should be careful when using this, because it actually clones the entire tree (via Parent
and Children
). Besides that, I think MemberwiseClone
copies the Children
and Parent
recursively. So by creating a new list for the Children
and calling DeepCopy()
on the Parent
you actually get a mix of copies and existing Node
objects, that can lead to unexpected behavior if you change either the copy or the original later on. And the child instances (other
) will not be part of the parents Children
list in the copy.
Why does other.Value
become a Node(Value)
? - Value
is by the way also covered by MemberwiseClone
.
Consider if it is of any use and possibly skip it. I can't see any use of it?
public void RemoveChild(Node node)
if (node != this && Children.Contains(node))
Children.Remove(node);
It is safe to call Children.Remove(node)
even if node
is null
, so you can spare the Contains()
check. node != this
- I suppose this should be avoided in the Add
method - but why can't this
be removed if provided as node
? You could consider to return the bool
values returned from Children.Remove(node)
, to let the client known if the operation was succesful or not.
You could consider to make the Node
class generic:
public class Node<T>
public T Value get;
...
As of GetChildrenRecursive()
it seems to work, but looks rather complicated as a BFS algorithm to be. Remember that you have private access to the properties and fields of Node
instances, so you can for instance call Children
on any Node
not just this
. Below is a revised version, that is a little easier to follow:
public IEnumerable<Node> GetChildrenRecursive()
if (!HasChildren) yield break;
Queue<Node> queue = new Queue<Node>(this.Children);
while (queue.Count > 0)
var node = queue.Dequeue();
yield return node;
if (node.HasChildren)
foreach (Node child in node.Children)
queue.Enqueue(child);
It uses yield return
instead of creating a concrete List<Node>
object which is more in line with the return value IEnumerable<Node>
.
edited 5 hours ago
answered 6 hours ago
Henrik HansenHenrik Hansen
10.9k1 gold badge14 silver badges38 bronze badges
10.9k1 gold badge14 silver badges38 bronze badges
1
$begingroup$
I think he meant GetChildrenRecursive as search function :) btw I reached my vote limit of today :(
$endgroup$
– dfhwze
6 hours ago
1
$begingroup$
@Henrik Hansen GetChildrenRecursive() is the method I'm talking about, if you are doing this because I got something mixed up (I can't blame you), then that went completely over my head. As of the DeepCopy(Node), I removed it, there was no use to it. And I'm planning to make this a generic type, I used theobject
type because gave more freedom over what can be inserted in the tree.
$endgroup$
– Stacklysm
5 hours ago
$begingroup$
And I will remove the null check when removing nodes
$endgroup$
– Stacklysm
5 hours ago
2
$begingroup$
Node<T> can always be used as Node<object> if you need to. So making it generic can't hurt your design.
$endgroup$
– dfhwze
5 hours ago
add a comment |
1
$begingroup$
I think he meant GetChildrenRecursive as search function :) btw I reached my vote limit of today :(
$endgroup$
– dfhwze
6 hours ago
1
$begingroup$
@Henrik Hansen GetChildrenRecursive() is the method I'm talking about, if you are doing this because I got something mixed up (I can't blame you), then that went completely over my head. As of the DeepCopy(Node), I removed it, there was no use to it. And I'm planning to make this a generic type, I used theobject
type because gave more freedom over what can be inserted in the tree.
$endgroup$
– Stacklysm
5 hours ago
$begingroup$
And I will remove the null check when removing nodes
$endgroup$
– Stacklysm
5 hours ago
2
$begingroup$
Node<T> can always be used as Node<object> if you need to. So making it generic can't hurt your design.
$endgroup$
– dfhwze
5 hours ago
1
1
$begingroup$
I think he meant GetChildrenRecursive as search function :) btw I reached my vote limit of today :(
$endgroup$
– dfhwze
6 hours ago
$begingroup$
I think he meant GetChildrenRecursive as search function :) btw I reached my vote limit of today :(
$endgroup$
– dfhwze
6 hours ago
1
1
$begingroup$
@Henrik Hansen GetChildrenRecursive() is the method I'm talking about, if you are doing this because I got something mixed up (I can't blame you), then that went completely over my head. As of the DeepCopy(Node), I removed it, there was no use to it. And I'm planning to make this a generic type, I used the
object
type because gave more freedom over what can be inserted in the tree.$endgroup$
– Stacklysm
5 hours ago
$begingroup$
@Henrik Hansen GetChildrenRecursive() is the method I'm talking about, if you are doing this because I got something mixed up (I can't blame you), then that went completely over my head. As of the DeepCopy(Node), I removed it, there was no use to it. And I'm planning to make this a generic type, I used the
object
type because gave more freedom over what can be inserted in the tree.$endgroup$
– Stacklysm
5 hours ago
$begingroup$
And I will remove the null check when removing nodes
$endgroup$
– Stacklysm
5 hours ago
$begingroup$
And I will remove the null check when removing nodes
$endgroup$
– Stacklysm
5 hours ago
2
2
$begingroup$
Node<T> can always be used as Node<object> if you need to. So making it generic can't hurt your design.
$endgroup$
– dfhwze
5 hours ago
$begingroup$
Node<T> can always be used as Node<object> if you need to. So making it generic can't hurt your design.
$endgroup$
– dfhwze
5 hours ago
add a comment |
Stacklysm is a new contributor. Be nice, and check out our Code of Conduct.
Stacklysm is a new contributor. Be nice, and check out our Code of Conduct.
Stacklysm is a new contributor. Be nice, and check out our Code of Conduct.
Stacklysm is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f224691%2frecursive-search-on-node-tree-with-linq-and-queue%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
$begingroup$
@dfhwze I'm sure there is a better/faster alternative to my approach to this.
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze Thinking now, If I don't find anything related to it in the MS assemblies, I'll just remove the tag
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
@dfhwze removed it
$endgroup$
– Stacklysm
7 hours ago
$begingroup$
meanwhile, I answered your question :) Btw, good to see unit tests in a question.
$endgroup$
– dfhwze
7 hours ago