using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Advanced.Algorithms.DataStructures { /// /// A trie (prefix tree) implementation. /// public class Trie : IEnumerable { private TrieNode root { get; set; } public int Count { get; private set; } public Trie() { root = new TrieNode(null, default(T)); Count = 0; } /// /// Insert a new record to this trie. /// Time complexity: O(m) where m is the length of entry. /// public void Insert(T[] entry) { insert(root, entry, 0); Count++; } /// /// Insert a new record to this trie after finding the end recursively. /// private void insert(TrieNode currentNode, T[] entry, int currentIndex) { while (true) { if (currentIndex == entry.Length) { currentNode.IsEnd = true; return; } if (currentNode.Children.ContainsKey(entry[currentIndex]) == false) { var newNode = new TrieNode(currentNode, entry[currentIndex]); currentNode.Children.Add(entry[currentIndex], newNode); currentNode = newNode; currentIndex = currentIndex + 1; } else { currentNode = currentNode.Children[entry[currentIndex]]; currentIndex = currentIndex + 1; } } } /// /// Deletes a record from this trie. /// Time complexity: O(m) where m is the length of entry. /// public void Delete(T[] entry) { delete(root, entry, 0); Count--; } /// /// Deletes a record from this trie after finding it recursively. /// private void delete(TrieNode currentNode, T[] entry, int currentIndex) { if (currentIndex == entry.Length) { if (!currentNode.IsEnd) { throw new Exception("Item not in trie."); } currentNode.IsEnd = false; return; } if (currentNode.Children.ContainsKey(entry[currentIndex]) == false) { throw new Exception("Item not in trie."); } delete(currentNode.Children[entry[currentIndex]], entry, currentIndex + 1); if (currentNode.Children[entry[currentIndex]].IsEmpty && !currentNode.IsEnd) { currentNode.Children.Remove(entry[currentIndex]); } } /// /// Returns a list of records matching this prefix. /// Time complexity: O(rm) where r is the number of results and m is the average length of each entry. /// public List StartsWith(T[] prefix) { return startsWith(root, prefix, 0); } /// /// Recursively visit until end of prefix /// and then gather all sub entries under it. /// private List startsWith(TrieNode currentNode, T[] searchPrefix, int currentIndex) { while (true) { if (currentIndex == searchPrefix.Length) { var result = new List(); //gather sub entries and prefix them with search entry prefix gatherStartsWith(result, searchPrefix, new List(), currentNode); return result; } if (currentNode.Children.ContainsKey(searchPrefix[currentIndex]) == false) { return new List(); } currentNode = currentNode.Children[searchPrefix[currentIndex]]; currentIndex = currentIndex + 1; } } /// /// Gathers all suffixes under this node appending with the given prefix. /// private void gatherStartsWith(List result, T[] searchPrefix, List suffix, TrieNode node) { //end of word if (node.IsEnd) { if (suffix != null) { result.Add(searchPrefix.Concat(suffix).ToArray()); } else { result.Add(searchPrefix); } } //visit all children foreach (var child in node.Children) { //append to end of prefix for new prefix suffix.Add(child.Key); gatherStartsWith(result, searchPrefix, suffix, child.Value); suffix.RemoveAt(suffix.Count - 1); } } /// /// Returns true if the entry exist. /// Time complexity: O(e) where e is the length of the given entry. /// public bool Contains(T[] entry) { return contains(root, entry, 0, false); } /// /// Returns true if any records match this prefix. /// Time complexity: O(e) where e is the length of the given entry. /// public bool ContainsPrefix(T[] prefix) { return contains(root, prefix, 0, true); } /// /// Find if the record exist recursively. /// private bool contains(TrieNode currentNode, T[] entry, int currentIndex, bool isPrefixSearch) { while (true) { if (currentIndex == entry.Length) { return isPrefixSearch || currentNode.IsEnd; } if (currentNode.Children.ContainsKey(entry[currentIndex]) == false) { return false; } currentNode = currentNode.Children[entry[currentIndex]]; currentIndex = currentIndex + 1; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { return new TrieEnumerator(root); } } internal class TrieNode { internal bool IsEmpty => Children.Count == 0; internal bool IsEnd { get; set; } internal TrieNode Parent { get; set; } internal Dictionary> Children { get; set; } internal T Value { get; set; } internal TrieNode(TrieNode parent, T value) { Parent = parent; Value = value; Children = new Dictionary>(); } } internal class TrieEnumerator : IEnumerator { private readonly TrieNode root; private Stack> progress; internal TrieEnumerator(TrieNode root) { this.root = root; } public bool MoveNext() { if (root == null) { return false; } if (progress == null) { progress = new Stack>(root.Children.Select(x => x.Value)); } while (progress.Count > 0) { var next = progress.Pop(); foreach (var child in next.Children) { progress.Push(child.Value); } if (next.IsEnd) { Current = getValue(next); return true; } } return false; } private T[] getValue(TrieNode next) { var result = new Stack(); result.Push(next.Value); while (next.Parent!=null && !next.Parent.Value.Equals(default(T))) { next = next.Parent; result.Push(next.Value); } return result.ToArray(); } public void Reset() { progress = null; Current = null; } public T[] Current { get; private set; } object IEnumerator.Current => Current; public void Dispose() { progress = null; } } }