CodeLiturgy.Dashboard/BlueWest.Collections/FastDictionary.cs

944 lines
29 KiB
C#
Raw Normal View History

2021-12-06 02:49:27 +03:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
namespace BlueWest.Collections
{
internal static class DictionaryHelper
{
/// <summary>
/// Minimum size we're willing to let hashtables be.
/// Must be a power of two, and at least 4.
/// Note, however, that for a given hashtable, the initial size is a function of the first constructor arg, and may be > kMinBuckets.
/// </summary>
internal const int kMinBuckets = 4;
/// <summary>
/// By default, if you don't specify a hashtable size at construction-time, we use this size. Must be a power of two, and at least kMinBuckets.
/// </summary>
internal const int kInitialCapacity = 32;
internal const int kPowerOfTableSize = 2048;
private readonly static int[] nextPowerOf2Table = new int[kPowerOfTableSize];
static DictionaryHelper()
{
for (int i = 0; i <= kMinBuckets; i++)
nextPowerOf2Table[i] = kMinBuckets;
for (int i = kMinBuckets + 1; i < kPowerOfTableSize; i++)
nextPowerOf2Table[i] = NextPowerOf2Internal(i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int NextPowerOf2(int v)
{
if (v < kPowerOfTableSize)
{
return nextPowerOf2Table[v];
}
else
{
return NextPowerOf2Internal(v);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int NextPowerOf2Internal(int v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
}
public class FastDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
const int InvalidNodePosition = -1;
public const uint kUnusedHash = 0xFFFFFFFF;
public const uint kDeletedHash = 0xFFFFFFFE;
// TLoadFactor4 - controls hash map load. 4 means 100% load, ie. hashmap will grow
// when number of items == capacity. Default value of 6 means it grows when
// number of items == capacity * 3/2 (6/4). Higher load == tighter maps, but bigger
// risk of collisions.
static int tLoadFactor = 6;
private struct Entry
{
public uint Hash;
public TKey Key;
public TValue Value;
public Entry ( uint hash, TKey key, TValue value)
{
this.Hash = hash;
this.Key = key;
this.Value = value;
}
}
private Entry[] _entries;
private int _capacity;
private int _initialCapacity; // This is the initial capacity of the dictionary, we will never shrink beyond this point.
private int _size; // This is the real counter of how many items are in the hash-table (regardless of buckets)
private int _numberOfUsed; // How many used buckets.
private int _numberOfDeleted; // how many occupied buckets are marked deleted
private int _nextGrowthThreshold;
private readonly IEqualityComparer<TKey> comparer;
public IEqualityComparer<TKey> Comparer
{
get { return comparer; }
}
public int Capacity
{
get { return _capacity; }
}
public int Count
{
get { return _size; }
}
public bool IsEmpty
{
get { return Count == 0; }
}
public FastDictionary(int initialBucketCount, IEnumerable<KeyValuePair<TKey, TValue>> src, IEqualityComparer<TKey> comparer)
: this(initialBucketCount, comparer)
{
Contract.Requires(src != null);
Contract.Ensures(_capacity >= initialBucketCount);
foreach (var item in src)
this[item.Key] = item.Value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FastDictionary(FastDictionary<TKey, TValue> src, IEqualityComparer<TKey> comparer)
: this(src._capacity, src, comparer)
{ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FastDictionary(FastDictionary<TKey, TValue> src)
: this(src._capacity, src, src.comparer)
{ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FastDictionary(int initialBucketCount, FastDictionary<TKey, TValue> src, IEqualityComparer<TKey> comparer)
{
Contract.Requires(src != null);
Contract.Ensures(_capacity >= initialBucketCount);
Contract.Ensures(_capacity >= src._capacity);
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
this._initialCapacity = DictionaryHelper.NextPowerOf2(initialBucketCount);
this._capacity = Math.Max(src._capacity, initialBucketCount);
this._size = src._size;
this._numberOfUsed = src._numberOfUsed;
this._numberOfDeleted = src._numberOfDeleted;
this._nextGrowthThreshold = src._nextGrowthThreshold;
int newCapacity = _capacity;
if (comparer == src.comparer)
{
// Initialization through copy (very efficient) because the comparer is the same.
this._entries = new Entry[newCapacity];
Array.Copy(src._entries, _entries, newCapacity);
}
else
{
// Initialization through rehashing because the comparer is not the same.
var entries = new Entry[newCapacity];
BlockCopyMemoryHelper.Memset(entries, new Entry(kUnusedHash, default(TKey), default(TValue)));
// Creating a temporary alias to use for rehashing.
this._entries = src._entries;
// This call will rewrite the aliases
Rehash(entries);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FastDictionary(IEqualityComparer<TKey> comparer)
: this(DictionaryHelper.kInitialCapacity, comparer)
{ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FastDictionary(int initialBucketCount, IEqualityComparer<TKey> comparer)
{
Contract.Ensures(_capacity >= initialBucketCount);
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
// Calculate the next power of 2.
int newCapacity = initialBucketCount >= DictionaryHelper.kMinBuckets ? initialBucketCount : DictionaryHelper.kMinBuckets;
newCapacity = DictionaryHelper.NextPowerOf2(newCapacity);
this._initialCapacity = newCapacity;
// Initialization
this._entries = new Entry[newCapacity];
BlockCopyMemoryHelper.Memset(this._entries, new Entry(kUnusedHash, default(TKey), default(TValue)));
this._capacity = newCapacity;
this._numberOfUsed = 0;
this._numberOfDeleted = 0;
this._size = 0;
this._nextGrowthThreshold = _capacity * 4 / tLoadFactor;
}
public FastDictionary(int initialBucketCount = DictionaryHelper.kInitialCapacity)
: this(initialBucketCount, EqualityComparer<TKey>.Default)
{ }
public void Add(TKey key, TValue value)
{
Contract.Ensures(this._numberOfUsed <= this._capacity);
Contract.EndContractBlock();
if (key == null)
throw new ArgumentNullException("key");
ResizeIfNeeded();
int hash = GetInternalHashCode(key);
int bucket = hash % _capacity;
uint uhash = (uint)hash;
int numProbes = 1;
do
{
uint nHash = _entries[bucket].Hash;
if (nHash == kUnusedHash)
{
_numberOfUsed++;
_size++;
goto SET;
}
if (nHash == uhash && comparer.Equals(_entries[bucket].Key, key))
throw new ArgumentException("Cannot add duplicated key.", "key");
bucket = (bucket + numProbes) % _capacity;
numProbes++;
}
while (true);
SET:
this._entries[bucket].Hash = uhash;
this._entries[bucket].Key = key;
this._entries[bucket].Value = value;
}
public bool Remove(TKey key)
{
Contract.Ensures(this._numberOfUsed < this._capacity);
if (key == null)
throw new ArgumentNullException("key");
int bucket = Lookup(key);
if (bucket == InvalidNodePosition)
return false;
SetDeleted(bucket);
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetDeleted(int node)
{
Contract.Ensures(_size <= Contract.OldValue<int>(_size));
if (_entries[node].Hash < kDeletedHash)
{
_entries[node].Hash = kDeletedHash;
_entries[node].Key = default(TKey);
_entries[node].Value = default(TValue);
_numberOfDeleted++;
_size--;
}
Contract.Assert(_numberOfDeleted >= Contract.OldValue<int>(_numberOfDeleted));
Contract.Assert(_entries[node].Hash == kDeletedHash);
if (3 * this._numberOfDeleted / 2 > this._capacity - this._numberOfUsed)
{
// We will force a rehash with the growth factor based on the current size.
Shrink(Math.Max(_initialCapacity, _size * 2));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ResizeIfNeeded()
{
if (_size >= _nextGrowthThreshold)
{
Grow(_capacity * 2);
}
}
private void Shrink(int newCapacity)
{
Contract.Requires(newCapacity > _size);
Contract.Ensures(this._numberOfUsed < this._capacity);
// Calculate the next power of 2.
newCapacity = Math.Max(DictionaryHelper.NextPowerOf2(newCapacity), _initialCapacity);
var entries = new Entry[newCapacity];
BlockCopyMemoryHelper.Memset(entries, new Entry(kUnusedHash, default(TKey), default(TValue)));
Rehash(entries);
}
public TValue this[TKey key]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Contract.Requires(key != null);
Contract.Ensures(this._numberOfUsed <= this._capacity);
int hash = GetInternalHashCode(key);
int bucket = hash % _capacity;
var entries = _entries;
uint nHash;
int numProbes = 1;
do
{
nHash = entries[bucket].Hash;
if (nHash == hash && comparer.Equals(entries[bucket].Key, key))
return entries[bucket].Value;
bucket = (bucket + numProbes) % _capacity;
numProbes++;
}
while (nHash != kUnusedHash);
throw new KeyNotFoundException();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
Contract.Requires(key != null);
Contract.Ensures(this._numberOfUsed <= this._capacity);
ResizeIfNeeded();
int hash = GetInternalHashCode(key);
int bucket = hash % _capacity;
uint uhash = (uint)hash;
int numProbes = 1;
do
{
uint nHash = _entries[bucket].Hash;
if (nHash == kUnusedHash)
{
_numberOfUsed++;
_size++;
goto SET;
}
if (nHash == uhash && comparer.Equals(_entries[bucket].Key, key))
goto SET;
bucket = (bucket + numProbes) % _capacity;
numProbes++;
}
while (true);
SET:
this._entries[bucket].Hash = uhash;
this._entries[bucket].Key = key;
this._entries[bucket].Value = value;
}
}
public void Clear()
{
this._entries = new Entry[_capacity];
BlockCopyMemoryHelper.Memset(this._entries, new Entry(kUnusedHash, default(TKey), default(TValue)));
this._numberOfUsed = 0;
this._numberOfDeleted = 0;
this._size = 0;
}
public bool Contains(TKey key)
{
Contract.Ensures(this._numberOfUsed <= this._capacity);
if (key == null)
throw new ArgumentNullException("key");
return (Lookup(key) != InvalidNodePosition);
}
private void Grow(int newCapacity)
{
Contract.Requires(newCapacity >= _capacity);
Contract.Ensures((_capacity & (_capacity - 1)) == 0);
var entries = new Entry[newCapacity];
BlockCopyMemoryHelper.Memset(entries, new Entry(kUnusedHash, default(TKey), default(TValue)));
Rehash(entries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(TKey key, out TValue value)
{
Contract.Requires(key != null);
Contract.Ensures(this._numberOfUsed <= this._capacity);
int hash = GetInternalHashCode(key);
int bucket = hash % _capacity;
var entries = _entries;
uint nHash;
int numProbes = 1;
do
{
nHash = entries[bucket].Hash;
if (nHash == hash && comparer.Equals(entries[bucket].Key, key))
{
value = entries[bucket].Value;
return true;
}
bucket = (bucket + numProbes) % _capacity;
numProbes++;
}
while (nHash != kUnusedHash);
value = default(TValue);
return false;
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <returns>Position of the node in the array</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int Lookup(TKey key)
{
int hash = GetInternalHashCode(key);
int bucket = hash % _capacity;
var entries = _entries;
uint uhash = (uint)hash;
uint numProbes = 1; // how many times we've probed
uint nHash;
do
{
nHash = entries[bucket].Hash;
if (nHash == hash && comparer.Equals(entries[bucket].Key, key))
return bucket;
bucket = (int)((bucket + numProbes) % _capacity);
numProbes++;
}
while (nHash != kUnusedHash);
return InvalidNodePosition;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetInternalHashCode(TKey key)
{
return comparer.GetHashCode(key) & 0x7FFFFFFF;
}
private void Rehash(Entry[] entries)
{
uint capacity = (uint)entries.Length;
var size = 0;
for (int it = 0; it < _entries.Length; it++)
{
uint hash = _entries[it].Hash;
if (hash >= kDeletedHash) // No interest for the process of rehashing, we are skipping it.
continue;
uint bucket = hash % capacity;
uint numProbes = 0;
while (!(entries[bucket].Hash == kUnusedHash))
{
numProbes++;
bucket = (bucket + numProbes) % capacity;
}
entries[bucket].Hash = hash;
entries[bucket].Key = _entries[it].Key;
entries[bucket].Value = _entries[it].Value;
size++;
}
this._capacity = entries.Length;
this._size = size;
this._entries = entries;
this._numberOfUsed = size;
this._numberOfDeleted = 0;
this._nextGrowthThreshold = _capacity * 4 / tLoadFactor;
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{
if (array == null)
throw new ArgumentNullException("The array cannot be null", "array");
if (array.Rank != 1)
throw new ArgumentException("Multiple dimensions array are not supporter", "array");
if (index < 0 || index > array.Length)
throw new ArgumentOutOfRangeException("index");
if (array.Length - index < Count)
throw new ArgumentException("The array plus the offset is too small.");
int count = _capacity;
var entries = _entries;
for (int i = 0; i < count; i++)
{
if (entries[i].Hash < kDeletedHash)
array[index++] = new KeyValuePair<TKey, TValue>(entries[i].Key, entries[i].Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return new Enumerator(this);
}
[Serializable]
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
{
private FastDictionary<TKey, TValue> dictionary;
private int index;
private KeyValuePair<TKey, TValue> current;
internal const int DictEntry = 1;
internal const int KeyValuePair = 2;
internal Enumerator(FastDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
this.index = 0;
this.current = new KeyValuePair<TKey, TValue>();
}
public bool MoveNext()
{
var count = dictionary._capacity;
var entries = dictionary._entries;
// Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
// dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue
while (index < count)
{
if (entries[index].Hash < kDeletedHash)
{
current = new KeyValuePair<TKey, TValue>(entries[index].Key, entries[index].Value);
index++;
return true;
}
index++;
}
index = count + 1;
current = new KeyValuePair<TKey, TValue>();
return false;
}
public KeyValuePair<TKey, TValue> Current
{
get { return current; }
}
public void Dispose()
{
}
object IEnumerator.Current
{
get
{
if (index == 0 || (index == dictionary._capacity + 1))
throw new InvalidOperationException("Can't happen.");
return new KeyValuePair<TKey, TValue>(current.Key, current.Value);
}
}
void IEnumerator.Reset()
{
index = 0;
current = new KeyValuePair<TKey, TValue>();
}
}
public KeyCollection Keys
{
get { return new KeyCollection(this); }
}
public ValueCollection Values
{
get { return new ValueCollection(this); }
}
public bool ContainsKey(TKey key)
{
if (key == null)
throw new ArgumentNullException("key");
return (Lookup(key) != InvalidNodePosition);
}
public bool ContainsValue(TValue value)
{
var entries = _entries;
int count = _capacity;
if (value == null)
{
for (int i = 0; i < count; i++)
{
if (entries[i].Hash < kDeletedHash && entries[i].Value == null)
return true;
}
}
else
{
EqualityComparer<TValue> c = EqualityComparer<TValue>.Default;
for (int i = 0; i < count; i++)
{
if (entries[i].Hash < kDeletedHash && c.Equals(entries[i].Value, value))
return true;
}
}
return false;
}
public sealed class KeyCollection : IEnumerable<TKey>, IEnumerable
{
private FastDictionary<TKey, TValue> dictionary;
public KeyCollection(FastDictionary<TKey, TValue> dictionary)
{
Contract.Requires(dictionary != null);
this.dictionary = dictionary;
}
public Enumerator GetEnumerator()
{
return new Enumerator(dictionary);
}
public void CopyTo(TKey[] array, int index)
{
if (array == null)
throw new ArgumentNullException("The array cannot be null", "array");
if (index < 0 || index > array.Length)
throw new ArgumentOutOfRangeException("index");
if (array.Length - index < dictionary.Count)
throw new ArgumentException("The array plus the offset is too small.");
int count = dictionary._capacity;
var entries = dictionary._entries;
for (int i = 0; i < count; i++)
{
if (entries[i].Hash < kDeletedHash)
array[index++] = entries[i].Key;
}
}
public int Count
{
get { return dictionary.Count; }
}
IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator()
{
return new Enumerator(dictionary);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(dictionary);
}
[Serializable]
public struct Enumerator : IEnumerator<TKey>, IEnumerator
{
private FastDictionary<TKey, TValue> dictionary;
private int index;
private TKey currentKey;
internal Enumerator(FastDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
index = 0;
currentKey = default(TKey);
}
public void Dispose()
{
}
public bool MoveNext()
{
var count = dictionary._capacity;
var entries = dictionary._entries;
while (index < count)
{
if (entries[index].Hash < kDeletedHash)
{
currentKey = entries[index].Key;
index++;
return true;
}
index++;
}
index = count + 1;
currentKey = default(TKey);
return false;
}
public TKey Current
{
get
{
return currentKey;
}
}
Object System.Collections.IEnumerator.Current
{
get
{
if (index == 0 || (index == dictionary.Count + 1))
throw new InvalidOperationException("Cant happen.");
return currentKey;
}
}
void System.Collections.IEnumerator.Reset()
{
index = 0;
currentKey = default(TKey);
}
}
}
public sealed class ValueCollection : IEnumerable<TValue>, IEnumerable
{
private FastDictionary<TKey, TValue> dictionary;
public ValueCollection(FastDictionary<TKey, TValue> dictionary)
{
Contract.Requires(dictionary != null);
this.dictionary = dictionary;
}
public Enumerator GetEnumerator()
{
return new Enumerator(dictionary);
}
public void CopyTo(TValue[] array, int index)
{
if (array == null)
throw new ArgumentNullException("The array cannot be null", "array");
if (index < 0 || index > array.Length)
throw new ArgumentOutOfRangeException("index");
if (array.Length - index < dictionary.Count)
throw new ArgumentException("The array plus the offset is too small.");
int count = dictionary._capacity;
var entries = dictionary._entries;
for (int i = 0; i < count; i++)
{
if (entries[i].Hash < kDeletedHash)
array[index++] = entries[i].Value;
}
}
public int Count
{
get { return dictionary.Count; }
}
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
{
return new Enumerator(dictionary);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(dictionary);
}
[Serializable]
public struct Enumerator : IEnumerator<TValue>, IEnumerator
{
private FastDictionary<TKey, TValue> dictionary;
private int index;
private TValue currentValue;
internal Enumerator(FastDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
index = 0;
currentValue = default(TValue);
}
public void Dispose()
{
}
public bool MoveNext()
{
var count = dictionary._capacity;
var entries = dictionary._entries;
while (index < count)
{
if (entries[index].Hash < kDeletedHash)
{
currentValue = entries[index].Value;
index++;
return true;
}
index++;
}
index = count + 1;
currentValue = default(TValue);
return false;
}
public TValue Current
{
get
{
return currentValue;
}
}
Object IEnumerator.Current
{
get
{
if (index == 0 || (index == dictionary.Count + 1))
throw new InvalidOperationException("Cant happen.");
return currentValue;
}
}
void IEnumerator.Reset()
{
index = 0;
currentValue = default(TValue);
}
}
}
private class BlockCopyMemoryHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Memset(Entry[] array, Entry value)
{
int block = 64, index = 0;
int length = Math.Min(block, array.Length);
//Fill the initial array
while (index < length)
{
array[index++] = value;
}
length = array.Length;
while (index < length)
{
Array.Copy(array, 0, array, index, Math.Min(block, (length - index)));
index += block;
block *= 2;
}
}
}
}
}