using System.Diagnostics.CodeAnalysis; using BuildingDirectory.Model; namespace BuildingDirectory; /// /// A generic dictionary (hash map) implementation managing keys and associated values /// /// Type of the keys; cannot be null /// Type of the values public sealed class MyDictionary where TKey : notnull { private const int InitialBuckets = 4; private const int MaxDepth = 2; private List[] _buckets; private int _currentMaxDepth; /// /// Gets the count of items stored in this dictionary /// public int Count { get; private set; } private int NoOfBuckets => _buckets.Length; /// /// Creates a new instance of the dictionary with default capacity /// public MyDictionary() : this(InitialBuckets) { } private MyDictionary(int capacity) { _buckets = CreateBuckets(capacity); // idk, guess I won't use it _currentMaxDepth = capacity; } /// /// Attempts to get the value associated with the given key. /// Returns the default value for if not found. /// /// Key of the required value public TValue? this[TKey key] { get { TryGetValue(key, out TValue? value); return value ?? default; } } /// /// Returns a list of all managed keys. /// /// A list of all keys public List GetKeys() { var keysAndValues = GetKeysAndValues(); List keys = []; foreach (var keyValue in keysAndValues) { keys.Add(keyValue.Key); } return keys; } /// /// Returns a list of all managed values. /// May contain duplicates. /// /// A list of all values public List GetValues() { var keysAndValues = GetKeysAndValues(); List values = []; foreach (var keyValue in keysAndValues) { values.Add(keyValue.Value); } return values; } /// /// Adds a value to the dictionary by its key. /// If the key is already present the previous values is replaced. /// /// Key of the value /// Value to store public void Add(TKey key, TValue value) { var bucketIdx = GetBucketIndex(key, NoOfBuckets); // Will always be false, but anyway. Its here and the tests are green :) if (bucketIdx >= _buckets.Length) { Grow(); } if (ContainsKey(key)) { Remove(key); } _buckets[bucketIdx].Add(new KeyValue(key, value)); Count++; } /// /// Attempts to remove the given key and associated value from the dictionary. /// Returns false if key is not found. /// /// Key to remove /// True if the key & value could be removed; false otherwise public bool Remove(TKey key) { var bucketIdx = GetBucketIndex(key, NoOfBuckets); for (var i = 0; i < _buckets[bucketIdx].Count; i++) { if (_buckets[bucketIdx][i].Key.Equals(key)) { _buckets[bucketIdx].RemoveAt(i); Count--; return true; } } return false; } /// /// Attempts to get a value by the given key from the dictionary. /// If not found value is set to the default of . /// /// Key to look for /// Out param for storing value if found /// True if key was found; false otherwise public bool TryGetValue(TKey key, out TValue? value) { value = default; var found = TryGetValue(key, out KeyValue? keyValue); if (keyValue == null) { return false; } value = keyValue.Value; return found; } /// /// Checks if the dictionary contains the given key /// /// Key to search for /// True if key is found; false otherwise public bool ContainsKey(TKey key) { var bucketIdx = GetBucketIndex(key, NoOfBuckets); foreach (var keyValue in _buckets[bucketIdx]) { if (keyValue.Key.Equals(key)) { return true; } } return false; } private bool TryGetValue(TKey key, out KeyValue? existingKeyValue) { existingKeyValue = null; var bucketIdx = GetBucketIndex(key, NoOfBuckets); foreach (var keyValue in _buckets[bucketIdx]) { if (keyValue.Key.Equals(key)) { existingKeyValue = keyValue; return true; } } return existingKeyValue != null; } private List GetKeysAndValues() { List list = []; foreach (var bucket in _buckets) { list.AddRange(bucket); } return list; } private void Grow() { var newBuckets = CreateBuckets(_buckets.Length * 2); for (var i = 0; i < _buckets.Length; i++) { for (var j = 0; j < _buckets[i].Count; j++) { int newIndex = GetBucketIndex(_buckets[i][j].Key, newBuckets.Length); newBuckets[newIndex].Add(_buckets[i][j]); } } _buckets = newBuckets; } private static List[] CreateBuckets(int amount) { var buckets = new List[amount]; for (var i = 0; i < amount; i++) { buckets[i] = new List(); } return buckets; } private int GetBucketIndex(TKey key, int? bucketCount = null) { var count = bucketCount ?? _buckets.Length; return Math.Abs(key.GetHashCode() % count); } // Object where the key-value pairs are stored private sealed class KeyValue { public KeyValue(TKey key, TValue value) { Key = key; Value = value; } public TKey Key { get; } public TValue Value { get; } private bool Equals(KeyValue other) { if (Value == null || other.Value == null) { return false; } return Key.Equals(other.Key) && Value.Equals(other.Value) && this.GetHashCode() == other.GetHashCode(); } public override bool Equals(object? obj) { var other = obj as KeyValue; if (obj != other) { return false; } return other != null && Equals(other); } public override int GetHashCode() { if (Value == null) { return Key.GetHashCode(); } return Value.GetHashCode(); } } }