-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTraversalExtensions.cs
More file actions
143 lines (126 loc) · 5.21 KB
/
TraversalExtensions.cs
File metadata and controls
143 lines (126 loc) · 5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
namespace Open.Hierarchy;
/// <summary>
/// Extensions for traversing a hierarchy (tree).
/// </summary>
public static class TraversalExtensions
{
private const string DescendantIsNotExpectedType = "Descendant is not of expected generic type and may create inconsistent results. May need to use non-generic node.GetDescenants method.";
// NOTE: not recursive, but could produce a large stack while traversing, possibly could be better, but this is a unidirectional hierarchy so it's not so easy to avoid recursion.
/// <summary>
/// Returns all descendant nodes.
/// </summary>
/// <param name="root">The root node to begin traversal.</param>
/// <param name="traversal">The mode by which the tree is traversed.</param>
/// <returns>The enumeration of all the descendant nodes.</returns>
public static IEnumerable<TNode> GetDescendantsOfType<TNode>(
this IParent<TNode> root, TraversalMode traversal = TraversalMode.BreadthFirst)
{
foreach (var descendant in root.GetDescendants(traversal))
{
yield return descendant is not TNode n
? throw new InvalidCastException(DescendantIsNotExpectedType)
: n;
}
}
/// <summary>
/// Returns all descendant nodes and includes this node.
/// Breadth first returns this node first. Depth first returns this node last.
/// </summary>
/// <param name="root">The root node to begin traversal.</param>
/// <param name="traversal">The mode by which the tree is traversed.</param>
/// <returns>The enumeration of all the descendant nodes including this one.</returns>
[SuppressMessage("Performance",
"HAA0601:Value type to reference type conversion causing boxing allocation",
Justification = "Incorrect assumption as TRoot is TNode.")]
public static IEnumerable<TNode> GetNodesOfType<TNode, TRoot>(
this TRoot root, TraversalMode traversal = TraversalMode.BreadthFirst)
where TRoot : TNode, IParent<TNode>
{
if (traversal == TraversalMode.BreadthFirst)
yield return root;
foreach (var descendant in root.GetDescendantsOfType(traversal))
yield return descendant;
if (traversal == TraversalMode.DepthFirst)
yield return root;
}
/// <summary>
/// Returns all descendant nodes and includes this node.
/// Breadth first returns this node first. Depth first returns this node last.
/// </summary>
/// <param name="root">The root node to begin traversal.</param>
/// <param name="traversal">The mode by which the tree is traversed.</param>
/// <returns>The enumeration of all the descendant nodes including this one.</returns>
public static IEnumerable<TNode> GetNodesOfType<TNode>(
this TNode root, TraversalMode traversal = TraversalMode.BreadthFirst)
where TNode : IParent<TNode>
=> root.GetNodesOfType<TNode, TNode>(traversal);
/// <summary>
/// Returns all descendant nodes.
/// </summary>
/// <param name="root">The root node to begin traversal.</param>
/// <param name="traversal">The mode by which the tree is traversed.</param>
/// <returns>The enumeration of all the descendant nodes.</returns>
public static IEnumerable<object> GetDescendants(
this IParent root, TraversalMode traversal = TraversalMode.BreadthFirst)
{
return root is null
? throw new ArgumentNullException(nameof(root))
: GetDescendantsCore(root, traversal);
static IEnumerable<object> GetDescendantsCore(IParent root, TraversalMode traversal)
{
// Attempt to be more breadth first.
switch (traversal)
{
case TraversalMode.BreadthFirst:
// Step 1: return the children.
foreach (var child in root.Children)
yield return child;
var grandchildren = root.Children
.OfType<IParent>()
.SelectMany(c => c.Children); // A snapshot might be helpful here, but not sure if it's worth it.
// Step 2: return the children.
foreach (var grandchild in grandchildren)
yield return grandchild;
// Step 3: return the other descendants. (not truly breadth first, but sufficient for now)
foreach (var descendant in grandchildren
.OfType<IParent>()
.SelectMany(c => c.GetDescendants()))
{
yield return descendant;
}
break;
case TraversalMode.DepthFirst:
// Simply walk the tree to the leaves recursively.
// Starting with the leaves and ending with the child.
foreach (var child in root.Children)
{
foreach (var descendant in root.Children
.OfType<IParent>()
.SelectMany(c => c.GetDescendants(TraversalMode.DepthFirst)))
{
yield return descendant;
}
yield return child;
}
break;
}
}
}
/// <summary>
/// Returns all descendant nodes and includes this node.
/// Breadth first returns this node first. Depth first returns this node last.
/// </summary>
/// <param name="root">The root node to begin traversal.</param>
/// <param name="traversal">The mode by which the tree is traversed.</param>
/// <returns>The enumeration of all the descendant nodes including this one.</returns>
public static IEnumerable<object> GetNodes(
this IParent root, TraversalMode traversal = TraversalMode.BreadthFirst)
{
if (traversal == TraversalMode.BreadthFirst)
yield return root;
foreach (var descendant in root.GetDescendants(traversal))
yield return descendant;
if (traversal == TraversalMode.DepthFirst)
yield return root;
}
}