diff --git a/MoreStructures.Tests/Dictionaries/BSTDict.cs b/MoreStructures.Tests/Dictionaries/BSTDict.cs new file mode 100644 index 00000000..a6bee284 --- /dev/null +++ b/MoreStructures.Tests/Dictionaries/BSTDict.cs @@ -0,0 +1,189 @@ +using MoreStructures.Dictionaries; + +namespace MoreStructures.Tests.Dictionaries; + +/// +/// A implementation based on a Binary Search Tree. +/// +public class BSTDict : IDict + where TKey : notnull, IComparable +{ + private sealed record Node(TKey Key, TValue Value, Node? Left, Node? Right) + { + public int Count { get; } = Left?.Count ?? 0 + Right?.Count ?? 0 + 1; + } + + private Node? Root { get; set; } = null; + + /// + /// + /// Retrieved from the count stored on the root of the BST. + ///
+ /// Time and Space Complexity are O(1). + ///
+ public int Count => Root?.Count ?? 0; + + /// + /// + /// Both retrieval and insertion are done by traversing the tree and looking for the node with key equal to the + /// provided key. + ///
+ /// Retrieval just returns the value of the node, if found, whereas insertion changes the value in the tree by + /// replacing the node or adding a new one. + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public TValue this[TKey key] + { + get + { + return Retrieve(Root); + + TValue Retrieve(Node? node) => node?.Key.CompareTo(key) switch + { + null => throw new KeyNotFoundException($"Couldn't find key '{key}' in the dictionary."), + 0 => node.Value, + > 0 => Retrieve(node.Left), + _ => Retrieve(node.Right), + }; + } + set + { + Root = InsertOrUpdate(Root); + + Node? InsertOrUpdate(Node? node) => node?.Key.CompareTo(key) switch + { + null => new(key, value, null, null), + 0 => new(key, value, node.Left, node.Right), + > 0 => new(node.Key, node.Value, InsertOrUpdate(node.Left), node.Right), + _ => new(node.Key, node.Value, node.Left, InsertOrUpdate(node.Right)), + }; + } + } + + /// + /// + /// Performs an in-order traversal of tree, yielding all the node keys. + ///
+ /// In this implementation, the order of is consistent with the order of . + ///
+ /// Time Complexity is O(n), when enumerated, where n is the number of items in the dictionary. + ///
+ /// Space Complexity is O(1). + ///
+ public IEnumerable Keys + { + get + { + return InOrderTraversal(Root); + + static IEnumerable InOrderTraversal(Node? node) + { + if (node == null) yield break; + InOrderTraversal(node.Left); + yield return node.Key; + InOrderTraversal(node.Right); + } + } + } + + /// + /// + /// Performs an in-order traversal of tree, yielding all the node values. + ///
+ /// In this implementation, the order of is consistent with the order of . + ///
+ /// Time Complexity is O(n), when enumerated, where n is the number of items in the dictionary. + ///
+ /// Space Complexity is O(1). + ///
+ public IEnumerable Values + { + get + { + return InOrderTraversal(Root); + + static IEnumerable InOrderTraversal(Node? node) + { + if (node == null) yield break; + InOrderTraversal(node.Left); + yield return node.Value; + InOrderTraversal(node.Right); + } + } + } + + /// + /// + /// Insertion is done by traversing the tree and looking for a node with key equal to the provided key, as in + /// the setter of . + ///
+ /// An is raised when an item with the same key as the one provided already exists + /// in the dictionary. + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public void Add(TKey key, TValue value) + { + Root = Insert(Root); + + Node? Insert(Node? node) => node?.Key.CompareTo(key) switch + { + null => new(key, value, null, null, 1), + 0 => throw new ArgumentException($"An item with the key '{key}' already exists in the dictionary."), + > 0 => Insert(node.Left), + _ => Insert(node.Right), + }; + } + + /// + /// + /// As in value retrieval, a traversal of the tree is done, looking for the node with key equal to the + /// provided key. + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public bool ContainsKey(TKey key) + { + return Find(Root); + + bool Find(Node? node) => node?.Key.CompareTo(key) switch + { + null => false, + 0 => true, + > 0 => Find(node.Left), + _ => Find(node.Right), + }; + } + + public TValue? Remove(TKey key) + { + throw new NotImplementedException(); + } + + /// + /// + /// Retrieval is done by traversing the tree and looking for a node with key equal to the provided key, as in + /// the getter of . + ///
+ /// is returned if such a mapping is not found, and is set to + /// the value for . + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public bool TryGetValue(TKey key, out TValue? value) + { + var (found, valueFound) = Find(Root); + value = found ? valueFound : default; + return found; + + (bool, TValue?) Find(Node? node) => node?.Key.CompareTo(key) switch + { + null => (false, default), + 0 => (true, node.Value), + > 0 => Find(node.Left), + _ => Find(node.Right), + }; + } +} + diff --git a/MoreStructures.Tests/Dictionaries/BSTDictTests.cs b/MoreStructures.Tests/Dictionaries/BSTDictTests.cs new file mode 100644 index 00000000..13b71ed8 --- /dev/null +++ b/MoreStructures.Tests/Dictionaries/BSTDictTests.cs @@ -0,0 +1,9 @@ +namespace MoreStructures.Tests.Dictionaries; + +[TestClass] +public class BSTDictTests : DictTests +{ + public BSTDictTests() : base(() => new BSTDict()) + { + } +} \ No newline at end of file diff --git a/MoreStructures.Tests/Dictionaries/DictTests.cs b/MoreStructures.Tests/Dictionaries/DictTests.cs new file mode 100644 index 00000000..d9228237 --- /dev/null +++ b/MoreStructures.Tests/Dictionaries/DictTests.cs @@ -0,0 +1,18 @@ +namespace MoreStructures.Tests.Dictionaries; + +public abstract class DictTests +{ + protected Func> Builder { get; } + + protected DictTests(Func> builder) + { + Builder = builder; + } + + [TestMethod] + public void Count_IsCorrect() + { + var dictionary = Builder(); + dictionary.Count(); + } +} diff --git a/MoreStructures/Dictionaries/BSTDict.cs b/MoreStructures/Dictionaries/BSTDict.cs new file mode 100644 index 00000000..b15c3c4a --- /dev/null +++ b/MoreStructures/Dictionaries/BSTDict.cs @@ -0,0 +1,250 @@ +namespace MoreStructures.Dictionaries; + +/// +/// A implementation based on a Binary Search Tree. +/// +public class BSTDict : IDict + where TKey : notnull, IComparable +{ + private sealed class Node + { + private Node? _left = null; + private Node? _right = null; + private int _count = 1; + + public TKey Key { get; set; } + public TValue Value { get; set; } + + public Node? Left { get => _left; set { _left = value; UpdateCount(); } } + public Node? Right { get => _right; set { _right = value; UpdateCount(); } } + public int Count => _count; + + public Node(TKey key, TValue value, BSTDict.Node? left, BSTDict.Node? right) + { + Key = key; + Value = value; + Left = left; + Right = right; + } + + private void UpdateCount() + { + _count = (_left?.Count ?? 0) + (_right?.Count ?? 0) + 1; + } + + public Node Mutate(Action mutation) + { + mutation(this); + return this; + } + } + + private static (bool found, TValue? valueFound) Find(Node? node, TKey key) => + node?.Key.CompareTo(key) switch + { + null => (false, default), + 0 => (true, node.Value), + > 0 => Find(node.Left, key), + _ => Find(node.Right, key), + }; + + private static IEnumerable InOrderTraversal(Node? node, Func valueProvider) + { + if (node == null) + yield break; + + foreach (var leftDescendant in InOrderTraversal(node.Left, valueProvider)) + yield return leftDescendant; + + yield return valueProvider(node); + + foreach (var rightDescendant in InOrderTraversal(node.Right, valueProvider)) + yield return rightDescendant; + } + + private Node? Root { get; set; } = null; + + /// + /// + /// Retrieved from the count stored on the root of the BST. + ///
+ /// Time and Space Complexity are O(1). + ///
+ public int Count => Root?.Count ?? 0; + + /// + /// + /// Both retrieval and insertion are done by traversing the tree from its root downwards, and looking for the node + /// with key equal to the provided . + ///
+ /// Retrieval just returns the value of the node, if found, raising otherwise. + ///
+ /// Insertion changes the value in the tree by replacing the node or adding a new one, also replacing all the nodes + /// in the path from the root to the insertion/update point. + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public TValue this[TKey key] + { + get + { + var (found, valueFound) = Find(Root, key); + if (!found) + throw new KeyNotFoundException($"Couldn't find key '{key}' in the dictionary."); + return valueFound!; + } + set + { + Root = InsertOrUpdate(Root); + + Node? InsertOrUpdate(Node? node) => node?.Key.CompareTo(key) switch + { + null => new(key, value, null, null), + 0 => node.Mutate(n => n.Value = value), + > 0 => node.Mutate(n => n.Left = InsertOrUpdate(node.Left)), + _ => node.Mutate(n => n.Right = InsertOrUpdate(node.Right)), + }; + } + } + + /// + /// + /// Performs an in-order traversal of tree, yielding all the node keys. + ///
+ /// In this implementation, the order of is consistent with the order of . + ///
+ /// Time Complexity is O(n), when enumerated, where n is the number of items in the dictionary. + ///
+ /// Space Complexity is O(1). Values are streamed to the client. + ///
+ public IEnumerable Keys => InOrderTraversal(Root, node => node.Key); + + /// + /// + /// Performs an in-order traversal of tree, yielding all the node values. + ///
+ /// In this implementation, the order of is consistent with the order of . + ///
+ /// Time Complexity is O(n), when enumerated, where n is the number of items in the dictionary. + ///
+ /// Space Complexity is O(1). Values are streamed to the client. + ///
+ public IEnumerable Values => InOrderTraversal(Root, node => node.Value); + + /// + /// + /// Insertion is done by traversing the tree and looking for a node with key equal to the provided key, as in + /// the setter of . + ///
+ /// Insertion replaces all nodes in the path from the root to the insertion point, recalculating summarization the + /// properties kept in tree nodes, such as the count of nodes in the subtree rooted at the node. + ///
+ /// An is raised when an item with the same key as the one provided already exists + /// in the dictionary, since key duplications is not allowed in implementations. + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public void Add(TKey key, TValue value) + { + Root = Insert(Root); + + Node? Insert(Node? node) => node?.Key.CompareTo(key) switch + { + null => new(key, value, null, null), + 0 => throw new ArgumentException($"An item with the key '{key}' already exists in the dictionary."), + > 0 => node.Mutate(n => n.Left = Insert(node.Left)), + _ => node.Mutate(n => n.Right = Insert(node.Right)), + }; + } + + /// + /// + /// As in value retrieval, a traversal of the tree is done, looking for the node with key equal to the + /// provided key. + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public bool ContainsKey(TKey key) => Find(Root, key).found; + + /// + /// + /// Performs Hibbard deletion: + ///
+ /// - first the node v, associated with the given , and its parent p, are retrieved from the + /// tree; + ///
+ /// - if v is not found, the deletion cannot be performed and the for + /// is returned; + ///
+ /// - if v is found and is a leaf, the reference to t in p is set to null, and no other change is made in the tree; + ///
+ /// - + ///
+ public TValue? Remove(TKey key) + { + var (node, parent) = Find(Root, null); + if (node == null) + return default; + + Remove(node); + + + static Node Remove(Node node) + { + // First case: the node is a leaf or has a single child + if (node.Left == null || node.Right == null) + { + var child = node.Left ?? node.Right; + if (parent == null) + Root = child; + else if (ReferenceEquals(parent.Left, node)) + parent.Left = child; + else + parent.Right = child; + + return node; + } + + // Second case: the node has two children + var leftChild = node.Left; + + + } + + static (Node? node, Node? parent) Find(Node? node, Node? parent) => + node?.Key.CompareTo(key) switch + { + null => (default, default), + 0 => (node, parent), + > 0 => Find(node.Left, node), + _ => Find(node.Right, node), + }; + } + + /// + /// + /// Retrieval is done by traversing the tree and looking for a node with key equal to the provided key, as in + /// the getter of . + ///
+ /// is returned if such a mapping is not found, and is set to + /// the value for . + ///
+ /// Time Complexity is O(h), where h is the height of the tree. Space Complexity is O(1). + ///
+ public bool TryGetValue(TKey key, out TValue? value) + { + var (found, valueFound) = Find(Root, key); + value = found ? valueFound : default; + return found; + } + + /// + /// + /// TODO + /// + public TValue? Remove(TKey key) + { + throw new NotImplementedException(); + } +} + diff --git a/MoreStructures/Dictionaries/IDict.cs b/MoreStructures/Dictionaries/IDict.cs new file mode 100644 index 00000000..557c3793 --- /dev/null +++ b/MoreStructures/Dictionaries/IDict.cs @@ -0,0 +1,79 @@ +namespace MoreStructures.Dictionaries; + +/// +/// A mapping between instances of and instances of , where +/// a key is associated to 0 or 1 value. +/// +/// The type of the key instances in the dictionary. +/// The type of the value instances in the dictionary. +public interface IDict + where TKey : notnull +{ + /// + /// The number of (key, value) mappings in this dictionary. + /// + int Count { get; } + + /// + /// Gets or inserts/updates the item with the specified . + /// + /// The key associated to the value to be retrieved. + /// + /// The value associated with the provided , if any. + ///
+ /// If such a key does not exist, a is thrown. + ///
+ TValue this[TKey key] { get; set; } + + /// + /// A sequence enumerating all the keys in this dictionary. The order is specific to the implementation. + /// + /// + /// In general, it's not guaranteed that the order of is the same as the order of + /// . + /// + IEnumerable Keys { get; } + + /// + /// A sequence enumerating all the values in this dictionary. The order is specific to the implementation. + /// + /// + /// In general, it's not guaranteed that the order of is the same as the order of + /// . + /// + IEnumerable Values { get; } + + /// + /// Adds a mapping between the provided and . + /// + /// The key of the mapping. + /// The value of the mapping. + void Add(TKey key, TValue value); + + /// + /// Whether this dictionary contains a mapping for the provided . + /// + /// The key, to look for in the dictionary. + /// Whether there is a mapping with the provided in this dictionary. + bool ContainsKey(TKey key); + + /// + /// Removes the mapping with the provided from this dictionary, if any. + /// + /// The key of the mapping to be removed. + /// The value of the mapping with the provided . + TValue? Remove(TKey key); + + /// + /// Tries to get the value for the provided , storing into the provided + /// reference. + /// + /// The key, to look for in the dictionary. + /// + /// Upon method return, it contains the value associated to , if such a mapping exists in the + /// dictionary. Returns the for otherwise. + /// + /// Whether the retrieval attempt was successful or not. + bool TryGetValue(TKey key, out TValue? value); +} +