using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Developpez.Dotnet.Collections { /// /// Représente un dictionnaire dont les clés sont de type string, implémenté /// à l'aide d'un trie (aussi appelé arbre préfixe) pour permettre une recherche /// rapide par préfixe de la clé. /// /// Type des valeurs du dictionnaire /// Les opérations de recherche par préfixe de clé dans un TrieDictionary /// sont en O(p), p étant la longueur du préfixe recherché. En effet, la complexité /// ne dépend pas du nombre d'éléments dans le dictionnaire, ce qui permet d'obtenir /// de très bonnes performances même avec des volumes de données importants. public class TrieDictionary : IDictionary { private int _count; private readonly TrieNode _rootNode; /// /// Crée une nouvelle instance de TrieDictionary /// public TrieDictionary() { _rootNode = new TrieNode('\0', null); } /// /// Crée une nouvelle instance de TrieDictionary contenant les éléments spécifiés /// /// Eléments à ajouter dans le dictionnaire public TrieDictionary(IEnumerable> entries) : this() { entries.CheckArgumentNull("entries"); foreach (var entry in entries) { Add(entry.Key, entry.Value); } } #region Core implementation private IEnumerable> Enumerate(TrieNode root, string prefix) { string key; if (root == _rootNode) key = prefix + string.Empty; else key = prefix + root.PartialKey; if (root.HasValue) yield return new KeyValuePair(key, root.Value); foreach (var kvp in root.Children) { foreach (var entry in Enumerate(kvp.Value, key)) { yield return entry; } } } private static TrieNode FindNode(TrieNode root, char[] key, int depth, bool create, out bool created) { created = false; if (depth == key.Length) return root; TrieNode node; if (root.Children.TryGetValue(key[depth], out node)) return FindNode(node, key, depth + 1, create, out created); if (create) { var current = root; node = null; for (int i = depth; i < key.Length; i++) { node = new TrieNode(key[i], current); current.Children.Add(key[i], node); current = node; } created = true; return node; } return null; } private TrieNode FindNode(string key, bool create, out bool created) { return FindNode(_rootNode, key.ToCharArray(), 0, create, out created); } private TrieNode FindNode(string key, bool create) { bool created; return FindNode(_rootNode, key.ToCharArray(), 0, create, out created); } private static void Prune(TrieNode removedNode) { var current = removedNode; while (current != null) { if (current.Children.Any() || current.HasValue) break; if (current.Parent != null) { current.Parent.Children.Remove(current.PartialKey); } current = current.Parent; } } class TrieNode { private readonly char _partialKey; private TValue _value; private bool _hasValue; private readonly TrieNode _parent; private readonly SortedDictionary _children; public TrieNode(char partialKey, TrieNode parent) { _partialKey = partialKey; _parent = parent; _children = new SortedDictionary(); } public TrieNode Parent { get { return _parent; } } public char PartialKey { get { return _partialKey; } } public bool HasValue { get { return _hasValue; } } public TValue Value { get { return _value; } set { _value = value; _hasValue = true; } } public void ClearValue() { _hasValue = false; } public IDictionary Children { get { return _children; } } } #endregion #region IDictionary implementation /// /// Retourne un énumérateur qui parcourt le dictionnaire /// /// Un énumerateur pour parcourir le dictionnaire public IEnumerator> GetEnumerator() { return Enumerate(_rootNode, null).GetEnumerator(); } /// /// Retourne un énumérateur qui parcourt le dictionnaire /// /// Un énumerateur pour parcourir le dictionnaire IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Ajoute la paire clé/valeur spécifiée au dictionnaire /// /// Paire clé/valeur à ajouter void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); } /// /// Supprime toutes les clés et les valeurs du dictionnaire. /// public void Clear() { _rootNode.ClearValue(); _rootNode.Children.Clear(); _count = 0; } /// /// Détermine si le dictionnaire contient la paire clé/valeur spécifiée. /// /// Paire clé/valeur recherchée /// true si le dictionnaire contient la paire clé/valeur, false sinon bool ICollection>.Contains(KeyValuePair item) { TValue value; if (TryGetValue(item.Key, out value)) return Equals(value, item.Value); return false; } /// /// Copie les éléments du dictionnaire dans un tableau, à partir de la position spécifiée /// /// Tableau de destination /// Index du tableau où commencer la copie void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (_count + arrayIndex > array.Length) throw new ArgumentException("The destination array is not large enough"); int index = arrayIndex; foreach (var item in Enumerate(_rootNode, null)) { array[index] = item; index++; } } /// /// Supprime du dictionnaire la paire clé/valeur spécifiée. /// /// Paire clé/valeur /// true si la recherche et la suppression de l'élément réussissent ; sinon, false. bool ICollection>.Remove(KeyValuePair item) { var node = FindNode(item.Key, false); if (node == null) return false; if (node.HasValue && Equals(node.Value, item.Value)) { node.ClearValue(); _count--; return true; } return false; } /// /// Obtient le nombre de paires clé/valeur contenues dans le dictionnaire. /// public int Count { get { return _count; } } /// /// Obtient une valeur indiquant si le dictionnaire est en lecture seule. /// public bool IsReadOnly { get { return false; } } /// /// Détermine si le dictionnaire contient la clé spécifiée. /// /// Clé à rechercher /// true si le dictionnaire contient un élément correspondant à la clé spécifiée ; sinon, false. public bool ContainsKey(string key) { key.CheckArgumentNull("key"); var node = FindNode(key, false); if (node != null) return node.HasValue; return false; } /// /// Ajoute la clé et la valeur spécifiées au dictionnaire. /// /// Clé de l'élément à ajouter. /// Valeur de l'élément à ajouter. La valeur peut être null pour les types référence. public void Add(string key, TValue value) { key.CheckArgumentNull("key"); var node = FindNode(key, true); if (node.HasValue) throw new ArgumentException("An element with the same key already exists in the trie."); node.Value = value; _count++; } /// /// Supprime du dictionnaire la valeur ayant la clé spécifiée. /// /// Clé de l'élément à supprimer. /// true si la recherche et la suppression de l'élément réussissent ; sinon, false. public bool Remove(string key) { var node = FindNode(key, false); if (node == null) return false; if (node.HasValue) { node.ClearValue(); Prune(node); _count--; return true; } return false; } /// /// Obtient la valeur associée à la clé spécifiée. /// /// Clé de la valeur à obtenir. /// Paramètre de sortie auquel est affecté la valeur trouvée /// true si le dictionnaire contient un élément correspondant à la clé spécifiée ; sinon, false. public bool TryGetValue(string key, out TValue value) { value = default(TValue); var node = FindNode(key, false); if (node == null) return false; if (node.HasValue) { value = node.Value; return true; } return false; } /// /// Obtient ou définit la valeur associée à la clé spécifiée. /// /// Clé de l'élément à obtenir ou à définir /// Valeur associée à la clé spécifiée. Si la clé spécifiée est introuvable, une opération Get retourne KeyNotFoundException et une opération Set crée un nouvel élément avec la clé spécifiée. public TValue this[string key] { get { key.CheckArgumentNull("key"); TValue value; if (TryGetValue(key, out value)) return value; throw new KeyNotFoundException(); } set { key.CheckArgumentNull("key"); bool created; var node = FindNode(key, true, out created); node.Value = value; if (created) _count++; } } /// /// Obtient une collection contenant les clés du dictionnaire. /// public ICollection Keys { get { return Enumerate(_rootNode, null).Select(kvp => kvp.Key).ToList(); } } /// /// Obtient une collection contenant les valeurs du dictionnaire. /// public ICollection Values { get { return Enumerate(_rootNode, null).Select(kvp => kvp.Value).ToList(); } } #endregion #region Public trie-specific methods /// /// Indique si le dictionnaire contient des paires clé/valeur dont la /// clé commence par le préfixe spécifié. /// /// Préfixe recherché /// true si le dictionnaire contient le préfixe spécifié, sinon false public bool ContainsPrefix(string prefix) { prefix.CheckArgumentNull("prefix"); var node = FindNode(prefix, false); return (node != null) && (node.HasValue || node.Children.Any()); } /// /// Enlève toutes les paires clé/valeur dont la clé commence par le /// préfixe spécifié /// /// Préfixe à supprimer /// Nombre d'éléments supprimés public int RemovePrefix(string prefix) { prefix.CheckArgumentNull("prefix"); var node = FindNode(prefix, false); if (node == null) return 0; int count = Enumerate(node, null).Count(); // Shouldn't happen if the tree is properly pruned... if (count == 0) return 0; node.Children.Clear(); node.ClearValue(); Prune(node); _count -= count; return count; } /// /// Retourne toutes les paires clé/valeur dont la clé commence par le /// préfixe spécifié. /// /// Préfixe recherché /// Séquence contenant toutes les paires clé/valeur correspondant au préfixe public IEnumerable> FindPrefix(string prefix) { prefix.CheckArgumentNull("prefix"); return FindPrefixIterator(prefix); } private IEnumerable> FindPrefixIterator(string prefix) { var node = FindNode(prefix, false); if (node == null) yield break; string prefix2 = null; if (prefix.Length > 0) prefix2 = prefix.Substring(0, prefix.Length - 1); foreach (var kvp in Enumerate(node, prefix2)) { yield return kvp; } } #endregion } }