using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Advanced.Algorithms.DataStructures { /// /// A ternary search tree implementation. /// public class TernarySearchTree : IEnumerable where T : IComparable { private TernarySearchTreeNode root; public int Count { get; private set; } public TernarySearchTree() { Count = 0; } /// /// Time complexity: O(m) where m is the length of entry. /// public void Insert(T[] entry) { insert(ref root, null, entry, 0); Count++; } /// /// Insert a new record to this ternary search tree after finding the end recursively. /// private void insert(ref TernarySearchTreeNode currentNode, TernarySearchTreeNode parent, T[] entry, int currentIndex) { //create new node if empty if (currentNode == null) { currentNode = new TernarySearchTreeNode(parent, entry[currentIndex]); } var compareResult = currentNode.Value.CompareTo(entry[currentIndex]); //current is greater? move left, move right otherwise //if current is equal then move center if (compareResult > 0) { //move left var left = currentNode.Left; insert(ref left, parent, entry, currentIndex); currentNode.Left = left; } else if (compareResult < 0) { //move right var right = currentNode.Right; insert(ref right, parent, entry, currentIndex); currentNode.Right = right; } else { if (currentIndex != entry.Length - 1) { //if equal we just skip to next element var middle = currentNode.Middle; insert(ref middle, currentNode, entry, currentIndex + 1); currentNode.Middle = middle; } //end of word else { if (currentNode.IsEnd) { throw new Exception("Item exists."); } currentNode.IsEnd = true; return; } } } /// /// Deletes a record from this ternary search tree. /// 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 TernarySearchTree after finding it recursively. /// private void delete(TernarySearchTreeNode currentNode, T[] entry, int currentIndex) { //empty node if (currentNode == null) { throw new Exception("Item not found."); } var compareResult = currentNode.Value.CompareTo(entry[currentIndex]); TernarySearchTreeNode child; //current is greater? move left, move right otherwise //if current is equal then move center if (compareResult > 0) { //move left child = currentNode.Left; delete(child, entry, currentIndex); //delete if middle is not end //and we if have'nt deleted the node yet if (child.HasChildren == false && !child.IsEnd) { currentNode.Left = null; } } else if (compareResult < 0) { //move right child = currentNode.Right; delete(child, entry, currentIndex); //delete if middle is not end //and we if have'nt deleted the node yet if (child.HasChildren == false && !child.IsEnd) { currentNode.Right = null; } } else { if (currentIndex != entry.Length - 1) { //if equal we just skip to next element child = currentNode.Middle; delete(child, entry, currentIndex + 1); //delete if middle is not end //and we if have'nt deleted the node yet if (child.HasChildren == false && !child.IsEnd) { currentNode.Middle = null; } } //end of word else { if (!currentNode.IsEnd) { throw new Exception("Item not found."); } //remove this end flag currentNode.IsEnd = false; return; } } } /// /// 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 suffixes under it. /// private List startsWith(TernarySearchTreeNode currentNode, T[] searchPrefix, int currentIndex) { while (true) { if (currentNode == null) { return new List(); } var compareResult = currentNode.Value.CompareTo(searchPrefix[currentIndex]); //current is greater? move left, move right otherwise //if current is equal then move center if (compareResult > 0) { //move left currentNode = currentNode.Left; continue; } if (compareResult < 0) { //move right currentNode = currentNode.Right; continue; } //end of search Prefix, so gather all words under it if (currentIndex != searchPrefix.Length - 1) { currentNode = currentNode.Middle; currentIndex = currentIndex + 1; continue; } var result = new List(); gatherStartsWith(result, searchPrefix.ToList(), currentNode.Middle); return result; } } /// /// Gathers all suffixes under this node appending with the given prefix /// private void gatherStartsWith(List result, List prefix, TernarySearchTreeNode node) { while (true) { if (node == null) { result.Add(prefix.ToArray()); return; } //end of word if (node.IsEnd) { //append to end of prefix for new prefix result.Add(prefix.Concat(new[] { node.Value }).ToArray()); } if (node.Left != null) { gatherStartsWith(result, prefix, node.Left); } if (node.Middle != null) { //append to end of prefix for new prefix prefix.Add(node.Value); gatherStartsWith(result, prefix, node.Middle); prefix.RemoveAt(prefix.Count - 1); } if (node.Right != null) { node = node.Right; continue; } break; } } /// /// 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 search(root, entry, 0, false); } /// /// Returns true if the entry prefix exist. /// Time complexity: O(e) where e is the length of the given entry. /// public bool ContainsPrefix(T[] entry) { return search(root, entry, 0, true); } /// /// Find if the record exist recursively. /// private bool search(TernarySearchTreeNode currentNode, T[] searchEntry, int currentIndex, bool isPrefixSearch) { while (true) { //create new node if empty if (currentNode == null) { return false; } //end of word, so return if (currentIndex == searchEntry.Length - 1) { return isPrefixSearch || currentNode.IsEnd; } var compareResult = currentNode.Value.CompareTo(searchEntry[currentIndex]); //current is greater? move left, move right otherwise //if current is equal then move center if (compareResult > 0) { //move left currentNode = currentNode.Left; continue; } if (compareResult < 0) { //move right currentNode = currentNode.Right; continue; } //if equal we just skip to next element currentNode = currentNode.Middle; currentIndex = currentIndex + 1; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { return new TernarySearchTreeEnumerator(root); } } internal class TernarySearchTreeNode where T : IComparable { internal bool IsEnd { get; set; } internal T Value { get; set; } internal bool HasChildren => !(Left == null && Middle == null && Right == null); internal TernarySearchTreeNode Parent { get; set; } internal TernarySearchTreeNode Left { get; set; } internal TernarySearchTreeNode Middle { get; set; } internal TernarySearchTreeNode Right { get; set; } internal TernarySearchTreeNode(TernarySearchTreeNode parent, T value) { Parent = parent; this.Value = value; } } internal class TernarySearchTreeEnumerator : IEnumerator where T : IComparable { private readonly TernarySearchTreeNode root; private Stack> progress; internal TernarySearchTreeEnumerator(TernarySearchTreeNode root) { this.root = root; } public bool MoveNext() { if (root == null) { return false; } if (progress == null) { progress = new Stack>(new[] { root }); } while (progress.Count > 0) { var next = progress.Pop(); foreach (var child in new[] { next.Left, next.Middle, next.Right }.Where(x => x != null)) { progress.Push(child); } if (next.IsEnd) { Current = getValue(next); return true; } } return false; } private T[] getValue(TernarySearchTreeNode 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; } } }