Skip to content

Commit c1b72cf

Browse files
author
Oren (electricessence)
committed
Initial checkin.
1 parent ae76a43 commit c1b72cf

4 files changed

Lines changed: 358 additions & 0 deletions

File tree

IHaveRoot.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Open.Hierarchy
2+
{
3+
public interface IHaveRoot<TRoot>
4+
{
5+
TRoot Root { get; }
6+
}
7+
}

IParent.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
3+
namespace Open.Hierarchy
4+
{
5+
public interface IParent
6+
{
7+
IReadOnlyList<object> Children { get; }
8+
}
9+
10+
public interface IParent<out TChild> : IParent
11+
{
12+
new IReadOnlyList<TChild> Children { get; }
13+
}
14+
15+
}

Node.cs

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Collections;
5+
using Open.Disposable;
6+
7+
namespace Open.Hierarchy
8+
{
9+
10+
public sealed class Node<T> : ICollection<Node<T>>, IHaveRoot<Node<T>>, IParent<Node<T>>
11+
{
12+
Node<T> _parent;
13+
public Node<T> Parent => _parent;
14+
15+
readonly List<Node<T>> _children;
16+
public IReadOnlyList<Node<T>> Children { get; private set; }
17+
IReadOnlyList<object> IParent.Children => Children;
18+
19+
public T Value { get; set; }
20+
21+
Node()
22+
{
23+
_children = new List<Node<T>>();
24+
Children = _children.AsReadOnly();
25+
}
26+
#region ICollection<Node<T>> Implementation
27+
public bool IsReadOnly => false;
28+
29+
public bool Contains(Node<T> node)
30+
{
31+
return _children.Contains(node);
32+
}
33+
34+
public bool Remove(Node<T> node)
35+
{
36+
if (_children.Remove(node))
37+
{
38+
node._parent = null; // Need to be very careful about retaining parent references as it may cause a 'leak' per-se.
39+
return true;
40+
}
41+
return false;
42+
}
43+
44+
public void Add(Node<T> node)
45+
{
46+
if (node._parent != null)
47+
{
48+
if (node._parent == this)
49+
throw new InvalidOperationException("Adding a child node more than once.");
50+
throw new InvalidOperationException("Adding a node that belongs to another parent.");
51+
}
52+
node._parent = this;
53+
_children.Add(node);
54+
}
55+
56+
public void Clear()
57+
{
58+
foreach (var c in _children)
59+
c._parent = null;
60+
_children.Clear();
61+
}
62+
63+
64+
public int Count => _children.Count;
65+
66+
public IEnumerator<Node<T>> GetEnumerator()
67+
{
68+
return _children.GetEnumerator();
69+
}
70+
71+
IEnumerator IEnumerable.GetEnumerator()
72+
{
73+
return GetEnumerator();
74+
}
75+
76+
public void CopyTo(Node<T>[] array, int arrayIndex)
77+
{
78+
_children.CopyTo(array, arrayIndex);
79+
}
80+
#endregion
81+
82+
public void Replace(Node<T> node, Node<T> replacement)
83+
{
84+
if (replacement._parent != null)
85+
throw new InvalidOperationException("Replacement node belongs to another parent.");
86+
var i = _children.IndexOf(node);
87+
if (i == -1)
88+
throw new InvalidOperationException("Node being replaced does not belong to this parent.");
89+
_children[i] = replacement;
90+
node._parent = null;
91+
replacement._parent = this;
92+
}
93+
94+
public void Detatch()
95+
{
96+
_parent?.Remove(this);
97+
_parent = null;
98+
}
99+
100+
101+
/// <summary>
102+
/// Iterates through all of the descendants of this node starting breadth first.
103+
/// </summary>
104+
/// <returns>All the descendants of this node.</returns>
105+
public IEnumerable<Node<T>> GetDescendants()
106+
{
107+
// Attempt to be more breadth first.
108+
109+
foreach (var child in _children)
110+
yield return child;
111+
112+
var grandchildren = _children.SelectMany(c => c);
113+
foreach (var grandchild in grandchildren)
114+
yield return grandchild;
115+
116+
foreach (var descendant in grandchildren.SelectMany(c => c.GetDescendants()))
117+
yield return descendant;
118+
}
119+
120+
/// <returns>This and all of its descendants.</returns>
121+
public IEnumerable<Node<T>> GetNodes()
122+
{
123+
yield return this;
124+
foreach (var descendant in GetDescendants())
125+
yield return descendant;
126+
}
127+
128+
/// <summary>
129+
/// Finds the root node of this tree.
130+
/// </summary>
131+
public Node<T> Root
132+
{
133+
get
134+
{
135+
var current = this;
136+
while (current._parent != null)
137+
current = current._parent;
138+
return current;
139+
}
140+
}
141+
142+
internal void Teardown(Factory factory = null)
143+
{
144+
Value = default(T);
145+
Detatch(); // If no parent then this does nothing...
146+
if (factory == null)
147+
{
148+
foreach (var c in _children)
149+
{
150+
c._parent = null; // Don't initiate a 'Detach' (which does a lookup) since we are clearing here;
151+
c.Teardown();
152+
}
153+
}
154+
else
155+
{
156+
foreach (var c in _children)
157+
{
158+
c._parent = null; // Don't initiate a 'Detach' (which does a lookup) since we are clearing here;
159+
factory.RecycleInternal(c);
160+
}
161+
}
162+
_children.Clear();
163+
}
164+
165+
166+
/// <summary>
167+
/// Used for mapping a tree of evaluations which do not have access to their parent nodes.
168+
/// </summary>
169+
public class Factory : DisposableBase
170+
{
171+
#region Creation, Recycling, and Disposal
172+
public Factory()
173+
{
174+
Pool = new ConcurrentQueueObjectPool<Node<T>>(
175+
() => new Node<T>(), PrepareForPool, ushort.MaxValue);
176+
}
177+
178+
protected override void OnDispose(bool calledExplicitly)
179+
{
180+
if (calledExplicitly)
181+
{
182+
DisposeOf(ref Pool);
183+
}
184+
}
185+
186+
ConcurrentQueueObjectPool<Node<T>> Pool;
187+
188+
public void Recycle(Node<T> n)
189+
{
190+
AssertIsAlive();
191+
192+
RecycleInternal(n);
193+
}
194+
195+
internal void RecycleInternal(Node<T> n)
196+
{
197+
var p = Pool;
198+
if (p == null) n.Teardown();
199+
else p.Give(n);
200+
}
201+
202+
void PrepareForPool(Node<T> n)
203+
{
204+
n.Teardown(this);
205+
}
206+
#endregion
207+
208+
// WARNING: Care must be taken not to have duplicate nodes anywhere in the tree but having duplicate values are allowed.
209+
210+
/// <summary>
211+
/// Clones a node by recreating the tree and copying the values.
212+
/// </summary>
213+
/// <param name="target">The node to replicate.</param>
214+
/// <param name="newParentForClone">If a parent is specified it will use that node as its parent. By default it ends up being detatched.</param>
215+
/// <param name="onNodeCloned">A function that recieves the old node and its clone.</param>
216+
/// <returns>The copy of the tree/branch.</returns>
217+
public Node<T> Clone(
218+
Node<T> target,
219+
Node<T> newParentForClone = null,
220+
Action<Node<T>, Node<T>> onNodeCloned = null)
221+
{
222+
AssertIsAlive();
223+
224+
var clone = Pool.Take();
225+
clone.Value = target.Value;
226+
newParentForClone?.Add(clone);
227+
228+
foreach (var child in target._children)
229+
clone.Add(Clone(child, clone, onNodeCloned));
230+
231+
onNodeCloned?.Invoke(target, clone);
232+
233+
return clone;
234+
}
235+
236+
237+
/// <summary>
238+
/// Clones a node by recreating the tree and copying the values.
239+
/// </summary>
240+
/// <param name="target">The node to replicate.</param>
241+
/// <param name="onNodeCloned">A function that recieves the old node and its clone.</param>
242+
/// <returns>The copy of the tree/branch.</returns>
243+
public Node<T> Clone(Node<T> target, Action<Node<T>, Node<T>> onNodeCloned)
244+
{
245+
return Clone(target, null, onNodeCloned);
246+
}
247+
248+
/// <summary>
249+
/// Create's a clone of the entire tree but only returns the clone of this node.
250+
/// </summary>
251+
/// <returns>A clone of this node as part of a newly cloned tree.</returns>
252+
public Node<T> CloneTree(Node<T> target)
253+
{
254+
Node<T> node = null;
255+
Clone(target.Root, (n, clone) =>
256+
{
257+
if (n == target) node = clone;
258+
});
259+
return node;
260+
}
261+
262+
/// <summary>
263+
/// Generates a full hierarchy if the root is an IParent and uses the root as the value of the hierarchy.
264+
/// Essentially building a map of the tree.
265+
/// </summary>
266+
/// <typeparam name="T">Child type.</typeparam>
267+
/// <typeparam name="TRoot">The type of the root.</typeparam>
268+
/// <param name="root">The root instance.</param>
269+
/// <returns>The full map of the root.</returns>
270+
public Node<T> Map<TRoot>(TRoot root)
271+
where TRoot : T
272+
{
273+
AssertIsAlive();
274+
275+
var current = Pool.Take();
276+
current.Value = root;
277+
278+
if (root is IParent<T> parent)
279+
{
280+
foreach (var child in parent.Children)
281+
{
282+
current.Add(Map<T>(child));
283+
}
284+
}
285+
286+
return current;
287+
}
288+
289+
/// <summary>
290+
/// Generates a full hierarchy if the root is an IParent and uses the root as the value of the hierarchy.
291+
/// Essentially building a map of the tree.
292+
/// </summary>
293+
/// <typeparam name="T">The type of the root.</typeparam>
294+
/// <param name="root">The root instance.</param>
295+
/// <returns>The full map of the root.</returns>
296+
public Node<T> Map(T root)
297+
{
298+
return Map<T>(root);
299+
}
300+
301+
/// <summary>
302+
/// Generates a full hierarchy if the root of the container is an IParent and uses the root as the value of the hierarchy.
303+
/// Essentially building a map of the tree.
304+
/// </summary>
305+
/// <typeparam name="T">The type of the root.</typeparam>
306+
/// <param name="container">The container of the root instance.</param>
307+
/// <returns>The full map of the root.</returns>
308+
public Node<T> Map<TRoot>(IHaveRoot<TRoot> container)
309+
where TRoot : T
310+
{
311+
return Map(container.Root);
312+
}
313+
314+
}
315+
316+
}
317+
318+
}

Open.Hierarchy.csproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp1.1</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<None Remove=".git" />
9+
<None Remove=".gitignore" />
10+
<None Remove="LICENSE" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Open.Disposable" Version="1.0.4" />
15+
<PackageReference Include="Open.Disposable.ObjectPools" Version="1.2.0" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)