using System.Collections;
using System.Collections.Generic;
public static void Main()
var tree = new NamespaceDirectoryTree
"Root.Sub11.Sub21.Sub12",
"Root.Sub11.Sub21.Sub13.Sub14",
"Root.Sub11.Sub21.Sub13.Sub14",
"Root.Sub11.Sub21.Sub13.Sub14",
"Root.Sub11.Sub21.Sub13.Sub14.Sub47"
foreach(var directory in tree)
System.Console.WriteLine(directory + ", ");
public class NamespaceDirectoryTree : ICollection<string>
private readonly NamespaceNode _root = new NamespaceNode(null);
public int Count => this._root.Count(n => n.Ends);
bool ICollection<string>.IsReadOnly => false;
public NamespaceDirectoryTree() { }
public NamespaceDirectoryTree(IEnumerable<string> namespaces)
foreach (string ns in namespaces)
this.AddPath(this._root, ns);
private void AddPath(NamespaceNode node, string ns)
throw new ArgumentNullException(nameof(ns));
NamespaceNode parent = node;
string parentNamespace = ns;
index = parentNamespace.IndexOf('.');
NamespaceNode child = parent.GetOrAddChild(parentNamespace);
NamespaceNode child = parent.GetOrAddChild(parentNamespace.Substring(0, index));
parentNamespace = parentNamespace.Substring(index + 1);
public void Add(string item) => this.AddPath(this._root, item);
public void Clear() => this._root.Children.Clear();
public bool Contains(string item) => this._root.EnumerateDirectories().Any(n => n == item);
bool ICollection<string>.Remove(string item) => throw new NotSupportedException();
public void CopyTo(string[] array, int arrayIndex)
foreach (string path in this._root.EnumerateDirectories())
array[arrayIndex++] = path;
public IEnumerator<string> GetEnumerator()
return this._root.EnumerateDirectories().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private sealed class NamespaceNode : IEnumerable<NamespaceNode>
public NamespaceNode(string? valueString)
this.Value = valueString;
this.Children = new Dictionary<string, NamespaceNode>(BucketStringComparer.Instance);
public bool Ends { get; set; }
public string? Value { get; }
public IDictionary<string, NamespaceNode> Children { get; }
public NamespaceNode GetOrAddChild(string path)
if (path is null || path.Length == 0)
throw new ArgumentNullException(nameof(path));
if (this.Children.TryGetValue(path, out NamespaceNode? child))
child = new NamespaceNode(path);
this.Children.Add(child.Value!, child);
public IEnumerable<string> EnumerateDirectories()
string separator = this.Children.Count == 1 && !this.Ends ? "." : "\\";
foreach (NamespaceNode child in this.Children.Values)
foreach (string path in child.EnumerateDirectories())
yield return String.Concat(this.Value, separator, path)!;
public IEnumerator<NamespaceNode> GetEnumerator()
var nodeBacklog = new Queue<NamespaceNode>(this.Children.Count);
nodeBacklog.Enqueue(this);
while (nodeBacklog.Count != 0)
NamespaceNode temp = nodeBacklog.Dequeue();
foreach (NamespaceNode child in temp.Children.Values)
nodeBacklog.Enqueue(child);
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
internal sealed class BucketStringComparer : IComparer<string>, IEqualityComparer<string>
private static readonly Lazy<BucketStringComparer> s_instance = new Lazy<BucketStringComparer>(() => new BucketStringComparer());
public static BucketStringComparer Instance => s_instance.Value;
public int Compare( string? x, string? y) => string.Compare(x, y);
public bool Equals(string? x, string? y) => string.Equals(x, y);
public int GetHashCode(string obj) => string.IsNullOrEmpty(obj) ? 0 : obj[0] + obj.Length;