using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; namespace FastEnumUtility.Internals; // note: // - I really don't want to make my own custom dictionary. // - However, this is faster than FrozonDictionary, so I have no choice but to prepare it. internal sealed class FastReadOnlyDictionary where TKey : notnull { #region Fields private readonly Entry[] _buckets; private readonly int _size; private readonly int _indexFor; private static readonly EqualityComparer s_comparer = EqualityComparer.Default; // JIT optimization #endregion #region Constructors private FastReadOnlyDictionary(Entry[] buckets, int size, int indexFor) { this._buckets = buckets; this._size = size; this._indexFor = indexFor; } #endregion #region Factories public static FastReadOnlyDictionary Create(IEnumerable source, Func keySelector, Func valueSelector) { const int initialSize = 4; const float loadFactor = 0.75f; var collectionSize = source.TryGetNonEnumeratedCount(out var count) ? count : initialSize; var capacity = calculateCapacity(collectionSize, loadFactor); var buckets = new Entry[capacity]; var indexFor = buckets.Length - 1; var size = 0; foreach (var x in source) { var key = keySelector(x); var value = valueSelector(x); var entry = new Entry(key, value, next: null); if (!tryAdd(buckets, entry, indexFor)) ThrowHelper.ThrowDuplicatedKeyExists(key); size++; } return new(buckets, size, indexFor); #region Local Functions static int calculateCapacity(int collectionSize, float loadFactor) { //--- Calculate estimate size var size = (int)(collectionSize / loadFactor); //--- Adjust to the power of 2 size--; size |= size >> 1; size |= size >> 2; size |= size >> 4; size |= size >> 8; size |= size >> 16; size += 1; //--- Set minimum size size = Math.Max(size, initialSize); return size; } static bool tryAdd(Entry[] buckets, Entry entry, int indexFor) { var hash = s_comparer.GetHashCode(entry.Key); var index = hash & indexFor; var target = buckets.At(index); if (target is null) { //--- Add new entry buckets.At(index) = entry; return true; } while (true) { //--- Check duplicate if (s_comparer.Equals(target.Key, entry.Key)) return false; //--- Append entry if (target.Next is null) { target.Next = entry; return true; } target = target.Next; } } #endregion } #endregion #region like IReadOnlyDictionary public int Count => this._size; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(TKey key) => this.TryGetValue(key, out _); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { var hash = s_comparer.GetHashCode(key); var index = hash & this._indexFor; var entry = this._buckets.At(index); while (entry is not null) { if (s_comparer.Equals(entry.Key, key)) { value = entry.Value; return true; } entry = entry.Next; } value = default; return false; } #endregion #region Nested Types private sealed class Entry(TKey key, TValue value, Entry? next) { public readonly TKey Key = key; public readonly TValue Value = value; public Entry? Next = next; } #endregion } internal sealed class CaseSensitiveStringDictionary { #region Fields private readonly Entry[] _buckets; private readonly int _size; private readonly int _indexFor; #endregion #region Constructors private CaseSensitiveStringDictionary(Entry[] buckets, int size, int indexFor) { this._buckets = buckets; this._size = size; this._indexFor = indexFor; } #endregion #region Factories public static CaseSensitiveStringDictionary Create(IEnumerable source, Func keySelector, Func valueSelector) { const int initialSize = 4; const float loadFactor = 0.75f; var collectionSize = source.TryGetNonEnumeratedCount(out var count) ? count : initialSize; var capacity = calculateCapacity(collectionSize, loadFactor); var buckets = new Entry[capacity]; var indexFor = buckets.Length - 1; var size = 0; foreach (var x in source) { var key = keySelector(x); var value = valueSelector(x); var entry = new Entry(key, value, next: null); if (!tryAdd(buckets, entry, indexFor)) ThrowHelper.ThrowDuplicatedKeyExists(key); size++; } return new(buckets, size, indexFor); #region Local Functions static int calculateCapacity(int collectionSize, float loadFactor) { //--- Calculate estimate size var size = (int)(collectionSize / loadFactor); //--- Adjust to the power of 2 size--; size |= size >> 1; size |= size >> 2; size |= size >> 4; size |= size >> 8; size |= size >> 16; size += 1; //--- Set minimum size size = Math.Max(size, initialSize); return size; } static bool tryAdd(Entry[] buckets, Entry entry, int indexFor) { var hash = StringHelper.CaseSensitive.GetHashCode(entry.Key); var index = hash & indexFor; var target = buckets.At(index); if (target is null) { //--- Add new entry buckets.At(index) = entry; return true; } while (true) { //--- Check duplicate if (StringHelper.CaseSensitive.Equals(target.Key, entry.Key)) return false; //--- Append entry if (target.Next is null) { target.Next = entry; return true; } target = target.Next; } } #endregion } #endregion #region like IReadOnlyDictionary public int Count => this._size; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(ReadOnlySpan key) => this.TryGetValue(key, out _); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(ReadOnlySpan key, [MaybeNullWhen(false)] out TValue value) { var hash = StringHelper.CaseSensitive.GetHashCode(key); var index = hash & this._indexFor; var entry = this._buckets.At(index); while (entry is not null) { if (StringHelper.CaseSensitive.Equals(key, entry.Key)) { value = entry.Value; return true; } entry = entry.Next; } value = default; return false; } #endregion #region Nested Types private sealed class Entry(string key, TValue value, Entry? next) { public readonly string Key = key; public readonly TValue Value = value; public Entry? Next = next; } #endregion } internal sealed class CaseInsensitiveStringDictionary { #region Fields private readonly Entry[] _buckets; private readonly int _size; private readonly int _indexFor; #endregion #region Constructors private CaseInsensitiveStringDictionary(Entry[] buckets, int size, int indexFor) { this._buckets = buckets; this._size = size; this._indexFor = indexFor; } #endregion #region Factories public static CaseInsensitiveStringDictionary Create(IEnumerable source, Func keySelector, Func valueSelector) { const int initialSize = 4; const float loadFactor = 0.75f; var collectionSize = source.TryGetNonEnumeratedCount(out var count) ? count : initialSize; var capacity = calculateCapacity(collectionSize, loadFactor); var buckets = new Entry[capacity]; var indexFor = buckets.Length - 1; var size = 0; foreach (var x in source) { var key = keySelector(x); var value = valueSelector(x); var entry = new Entry(key, value, next: null); if (!tryAdd(buckets, entry, indexFor)) ThrowHelper.ThrowDuplicatedKeyExists(key); size++; } return new(buckets, size, indexFor); #region Local Functions static int calculateCapacity(int collectionSize, float loadFactor) { //--- Calculate estimate size var size = (int)(collectionSize / loadFactor); //--- Adjust to the power of 2 size--; size |= size >> 1; size |= size >> 2; size |= size >> 4; size |= size >> 8; size |= size >> 16; size += 1; //--- Set minimum size size = Math.Max(size, initialSize); return size; } static bool tryAdd(Entry[] buckets, Entry entry, int indexFor) { var hash = StringHelper.CaseInsensitive.GetHashCode(entry.Key); var index = hash & indexFor; var target = buckets.At(index); if (target is null) { //--- Add new entry buckets.At(index) = entry; return true; } while (true) { //--- Check duplicate if (StringHelper.CaseInsensitive.Equals(target.Key, entry.Key)) return false; //--- Append entry if (target.Next is null) { target.Next = entry; return true; } target = target.Next; } } #endregion } #endregion #region like IReadOnlyDictionary public int Count => this._size; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(ReadOnlySpan key) => this.TryGetValue(key, out _); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(ReadOnlySpan key, [MaybeNullWhen(false)] out TValue value) { var hash = StringHelper.CaseInsensitive.GetHashCode(key); var index = hash & this._indexFor; var entry = this._buckets.At(index); while (entry is not null) { if (StringHelper.CaseInsensitive.Equals(entry.Key, key)) { value = entry.Value; return true; } entry = entry.Next; } value = default; return false; } #endregion #region Nested Types private sealed class Entry(string key, TValue value, Entry? next) { public readonly string Key = key; public readonly TValue Value = value; public Entry? Next = next; } #endregion }