using System;
using System.Collections.Generic;
namespace DataStructures.Tries
{
/// <summary>
/// A Trie is a data structure (particular case of m-ary tree) used to efficiently represent strings with common prefixes.
/// Originally posed by E. Fredkin in 1960.
/// Fredkin, Edward (Sept. 1960), "Trie Memory", Communications of the ACM 3 (9): 490-499.
/// Its name is due to retrieval because its main application is in the field of "Information Retrieval" (information retrieval).
/// </summary>
public class Trie
{
/// <summary>
/// This character marks the end of a string.
/// </summary>
private const char Mark = '$';
/// <summary>
/// This property represents the root node of the trie.
/// </summary>
private readonly TrieNode root;
/// <summary>
/// Initializes a new instance of the <see cref="Trie"/> class. This instances was created without text strings, generating the root node of the trie, without children.
/// </summary>
public Trie()
{
root = new TrieNode(Mark);
}
/// <summary>
/// Initializes a new instance of the <see cref="Trie"/> class. Given a set of text strings, each of those strings inserts them into the trie using the Insert (string) method.
/// </summary>
/// <param name="words">The array with text strings to insert in the trie.</param>
public Trie(IEnumerable<string> words)
: this()
{
foreach (string s in words)
{
Insert(s);
}
}
/// <summary>
/// Insert a string s to the trie. The $ mark is added to the end of the chain and then it is added, this in order to indicate the end of the chain in the trie.
/// </summary>
/// <param name="s">The string to insert into the trie.</param>
public void Insert(string s)
{
s += Mark;
int index = 0;
TrieNode match = PrefixQuery(s, ref index);
for (int i = index; i < s.Length; i++)
{
TrieNode t = new(s[i], match);
match[s[i]] = t;
match = t;
}
}
/// <summary>
/// Remove a text string from the trie.
/// </summary>
/// <param name="s">The text string to be removed from the trie.</param>
public void Remove(string s)
{
s += Mark;
int index = 0;
TrieNode match = PrefixQuery(s, ref index);
while(match.IsLeaf())
{
char c = match.Value;
if(match.Parent == null)
{
break;
}
match = match.Parent;
match.Children.Remove(c);
}
}
/// <summary>
/// Know if a text string is in the trie.
/// </summary>
/// <param name="s">The string s that you want to know if it is in the trie.</param>
/// <returns>If the string is found, it returns true, otherwise false.</returns>
public bool Find(string s)
{
int index = 0;
return PrefixQuery(s + Mark, ref index).IsLeaf();
}
/// <summary>
/// This method analyzes which is the longest common prefix of a string s in the trie. If the string is in the trie then it is equivalent to doing Find (s).
/// </summary>
/// <param name="s">The string for which you want to know the longest common prefix.</param>
/// <param name="index">The index to which the longest common prefix goes.</param>
/// <returns>
/// Returns the longest common prefix node found in the trie with the string s.
/// </returns>
private TrieNode PrefixQuery(string s, ref int index)
{
TrieNode current = root;
for (int i = 0; i < s.Length && current != null; i++)
{
if (current[s[i]] != null)
{
current = current[s[i]] ?? throw new NullReferenceException();
index = i + 1;
}
else
{
break;
}
}
return current ?? throw new NullReferenceException();
}
}
}