JD2022-TU1/main/tools/framework/JD.Collections/Details/ShortHash.cs

719 lines
No EOL
22 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Permissions;
using JD.Collections.Extensions;
using JD.Kernel.StaticHelpers;
namespace JD.Collections.Details
{
/// <summary>
/// Stores values using a hasing algorithm.
/// </summary>
/// <remarks>Should eventually replace Hash</remarks>
[Serializable, ComVisible(false), DebuggerDisplay("Count = {Count}")]
public partial class ShortHash<T> : IHash<T>, ISerializable, IDeserializationCallback, IVariableSizeHash<T>
{
#region Constants
private const int GrowFactor = 2;
private const ushort InvalidIndex = 0;
public const ushort MaxCount = ushort.MaxValue - 2;
public const ushort MaxIndex = ushort.MaxValue - 1;
private const int NonZeroMask = 0x00000001; // Make sure hashes are not 0, it means an empty hash.
private const int PositiveMask = 0x7FFFFFFF; // Make sure hashes are not negatives or module returns negative indexes
#endregion
#region Variables
private ushort[] m_table;
private readonly IEqualityComparer<T> m_comparer;
private Entry[] m_entries; // Entries table, first slot is always empty so that 0 means invalid index --> removes the need to initialize the collection.
private ushort m_nextFreeSlot = InvalidIndex;
private ushort m_insertIndex = 1;
private ushort m_freeCount;
private int m_version;
[NonSerialized]
private SerializationInfo m_serializationInfo;
#endregion
#region Constructor
public ShortHash()
: this(0, null)
{
}
public ShortHash(IEqualityComparer<T> comparer)
: this(0, comparer)
{
}
public ShortHash(int capacity)
: this(capacity, null)
{
}
public ShortHash(int capacity, IEqualityComparer<T> comparer)
{
Throw.ArgumentOutOfRangeIf(capacity < 0, "capacity", "capacity must be positive");
Initialize(capacity);
m_comparer = comparer ?? EqualityComparer<T>.Default;
}
protected ShortHash(SerializationInfo serializationInfo, StreamingContext context)
{
m_insertIndex = 1;
m_freeCount = 0;
m_nextFreeSlot = InvalidIndex;
m_serializationInfo = serializationInfo;
m_comparer = (IEqualityComparer<T>)m_serializationInfo.GetValue("Comparer", typeof(IEqualityComparer<T>));
}
protected ShortHash(ShortHash<T> other)
: this(other.Count, other.Comparer)
{
other.ForEach(Add);
}
#endregion
#region Properties
public IEqualityComparer<T> Comparer
{
get
{
return m_comparer;
}
}
public int Count
{
get
{
return m_insertIndex - 1 - m_freeCount;
}
}
/// <summary>
/// Get or set the count of items the hash can contain (cannot be less than the current size).
/// </summary>
public int Capacity
{
get { return (ushort)(m_entries.Length - 1); }
set
{
if (value > Capacity)
{
ResizeHashTable(value);
ResizeEntries(value + 1);
}
}
}
/// <summary>
/// Enumerates the content of the hash but does not garantee to return elements added after the enumeration started. Garantees to not throw or not cause any threading issue.
/// </summary>
public IEnumerable<T> ThreadSafeEnumerate
{
get
{
var values = m_entries;
for (int i = 0; i < values.Length; i++)
{
var entry = values[i];
if (!entry.Empty)
{
yield return entry.Value;
}
}
}
}
public T this[T key]
{
get
{
T result;
TryGetValue(key, out result);
return result;
}
}
public int Version
{
get { return m_version; }
}
#endregion
#region Public Methods
/// <summary>
/// Make sure current enumerations will crash on next MoveNext().
/// </summary>
public void StopEnumerations()
{
m_version++;
}
public void TrimExcess()
{
if (m_entries.Length < m_insertIndex)
{
Entry[] entries = new Entry[m_insertIndex];
Array.Copy(m_entries, entries, m_insertIndex);
m_entries = entries;
}
}
public void Add(T value)
{
Insert(value, CollisionBehavior.Throw);
}
public void AddRange(IEnumerable<T> values)
{
Capacity += values.Count();
foreach (var value in values)
{
Insert(value, CollisionBehavior.Throw);
}
}
/// <summary>
/// Adds a new item to the set. If the set already contains an item equal to
/// <paramref name="item"/>, that item is replaced with <paramref name="item"/>.
/// </summary>
/// <remarks>
/// <para>Equality between items is determined by the comparison instance or delegate used to create the set.</para>
/// <para>Adding an item takes approximately constant time, regardless of the number of items in the set.</para></remarks>
/// <param name="item">The item to add to the set.</param>
/// <returns>True if it added the value (if it wasn't present).</returns>
public bool TryAdd(T item)
{
return Insert(item, CollisionBehavior.Replace);
}
public void Clear()
{
if (m_insertIndex > 1)
{
Array.Clear(m_table, 0, m_table.Length);
Array.Clear(m_entries, 0, m_insertIndex);
m_nextFreeSlot = InvalidIndex;
m_insertIndex = 1;
m_freeCount = 0;
m_version++;
}
}
public bool Remove(T value)
{
ushort hash;
unchecked
{
hash = (ushort)((m_comparer.GetSupportingNullHashCode(value) | NonZeroMask) & PositiveMask);
}
int startIndex = hash % m_table.Length;
int previousIndex = InvalidIndex;
for (var index = m_table[startIndex]; index != InvalidIndex; index = m_entries[index].Next)
{
if (m_entries[index].HashCode == hash && m_comparer.Equals(m_entries[index].Value, value))
{
if (previousIndex != InvalidIndex)
{
m_entries[previousIndex].Next = m_entries[index].Next;
}
else
{
// Removing first entry of list.
m_table[startIndex] = m_entries[index].Next;
}
m_entries[index] = new Entry { Next = m_nextFreeSlot };
m_nextFreeSlot = index;
m_freeCount++;
m_version++;
return true;
}
previousIndex = index;
}
return false;
}
public bool Remove(ILookup<T> lookup, out T removedValue)
{
ushort hash;
unchecked
{
hash = (ushort)((lookup.HashCode | NonZeroMask) & PositiveMask);
}
int startIndex = hash % m_table.Length;
int previousIndex = InvalidIndex;
for (var index = m_table[startIndex]; index != InvalidIndex; index = m_entries[index].Next)
{
if (m_entries[index].HashCode == hash && lookup.Equals(m_entries[index].Value))
{
if (previousIndex != InvalidIndex)
{
m_entries[previousIndex].Next = m_entries[index].Next;
}
else
{
// Removing first entry of list.
m_table[startIndex] = m_entries[index].Next;
}
removedValue = m_entries[index].Value;
m_entries[index].Next = m_nextFreeSlot;
m_entries[index].HashCode = InvalidIndex;
m_entries[index].Value = default(T);
m_nextFreeSlot = index;
m_freeCount++;
m_version++;
return true;
}
previousIndex = index;
}
removedValue = default(T);
return false;
}
public bool Contains(T value)
{
ushort hash;
unchecked
{
hash = (ushort)((m_comparer.GetSupportingNullHashCode(value) | NonZeroMask) & PositiveMask);
}
for (var i = m_table[hash % m_table.Length]; i != InvalidIndex; i = m_entries[i].Next)
{
if ((m_entries[i].HashCode == hash) && m_comparer.Equals(m_entries[i].Value, value))
{
return true;
}
}
return false;
}
public bool TryGetValue(T value, out T result)
{
int index = FindEntry(value);
if (index != InvalidIndex)
{
result = m_entries[index].Value;
return true;
}
result = default(T);
return false;
}
public bool TryGetValue(ILookup<T> lookup, out T result)
{
int index = FindEntry(lookup);
if (index != InvalidIndex)
{
result = m_entries[index].Value;
return true;
}
result = default(T);
return false;
}
public ShortHash<T> Clone()
{
return new ShortHash<T>(this);
}
IHash<T> IHash<T>.Clone()
{
return Clone();
}
#endregion
#region Private Methods
/// <summary>
/// Not Using the Find with lookup to remove the overhead of creating an object for each search.
/// </summary>
private int FindEntry(T value)
{
ushort hash;
unchecked
{
hash = (ushort)((m_comparer.GetSupportingNullHashCode(value) | NonZeroMask) & PositiveMask);
}
for (var i = m_table[hash % m_table.Length]; i != InvalidIndex; i = m_entries[i].Next)
{
if ((m_entries[i].HashCode == hash) && m_comparer.Equals(m_entries[i].Value, value))
{
return i;
}
}
return InvalidIndex;
}
private int FindEntry(ILookup<T> lookup)
{
Throw.IfNull(lookup, "lookup");
ushort hash;
unchecked
{
hash = (ushort)((lookup.HashCode | NonZeroMask) & PositiveMask);
}
for (var i = m_table[hash % m_table.Length]; i != InvalidIndex; i = m_entries[i].Next)
{
if ((m_entries[i].HashCode == hash) && lookup.Equals(m_entries[i].Value))
{
return i;
}
}
return InvalidIndex;
}
private void Initialize(int capacity)
{
int prime = HashHelpers.GetPrime(capacity);
m_table = new ushort[prime];
for (int i = 0; i < m_table.Length; i++)
{
m_table[i] = InvalidIndex;
}
m_entries = new Entry[Math.Min(Math.Max(2, capacity), MaxCount + 1)];
InitEntries(m_entries, 0);
m_nextFreeSlot = InvalidIndex;
}
private static void InitEntries(Entry[] entries, int startIndex)
{
int length = entries.Length;
for (int i = startIndex; i < length; i++)
{
entries[i].HashCode = InvalidIndex;
entries[i].Next = InvalidIndex;
}
}
private bool Insert(T value, CollisionBehavior collisionBehavior)
{
if (Count + 1 > m_table.Length)
{
ResizeHashTable(m_table.Length * GrowFactor);
}
ushort hash;
unchecked
{
hash = (ushort)((m_comparer.GetSupportingNullHashCode(value) | NonZeroMask) & PositiveMask);
}
int startIndex = hash % m_table.Length;
int lastIndex = InvalidIndex;
for (var index = m_table[startIndex]; index != InvalidIndex; index = m_entries[index].Next)
{
if (m_entries[index].Is(hash, value, m_comparer))
{
if (collisionBehavior == CollisionBehavior.Throw)
{
throw new ArgumentException($"Item {value} is already present in dictionary", "value");
}
else if (collisionBehavior == CollisionBehavior.Replace)
{
m_entries[index].Value = value;
m_version++;
}
return false;
}
lastIndex = index;
}
// Found a collision
if (lastIndex != InvalidIndex)
{
var index = InsertEntry(value, hash);
m_entries[lastIndex].Next = index;
}
else
{
m_table[startIndex] = InsertEntry(value, hash);
}
return true;
}
private ushort InsertEntry(T value, ushort hash)
{
m_version++;
ushort index;
if (m_nextFreeSlot != InvalidIndex)
{
index = m_nextFreeSlot;
m_nextFreeSlot = m_entries[index].Next;
m_freeCount--;
}
else
{
if (m_insertIndex == MaxIndex)
{
throw new InvalidOperationException("Hash has reach its maximum element count");
}
if (m_entries.Length <= m_insertIndex)
{
ResizeEntries();
}
index = m_insertIndex;
m_insertIndex++;
}
m_entries[index].HashCode = hash;
m_entries[index].Value = value;
m_entries[index].Next = InvalidIndex;
return index;
}
private void ResizeHashTable(int expectedSize)
{
int prime = HashHelpers.GetPrime(expectedSize);
var newTable = new ushort[prime];
for (ushort i = 0; i < m_entries.Length; i++)
{
if (!m_entries[i].Empty)
{
int insertIndex = m_entries[i].HashCode % prime;
m_entries[i].Next = newTable[insertIndex];
newTable[insertIndex] = i;
}
else
{
m_entries[i].Next = InvalidIndex;
}
}
m_table = newTable;
}
private void ResizeEntries()
{
ResizeEntries(m_entries.Length * 2);
}
private void ResizeEntries(int entryCount)
{
Entry[] entries = new Entry[Math.Min(entryCount, MaxCount + 1)];
Array.Copy(m_entries, entries, m_insertIndex);
m_entries = entries;
}
#endregion
#region Serialization
[SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
Throw.IfNull(info, "info");
info.AddValue("Comparer", m_comparer);
info.AddValue("Values", this.ToArray());
}
public void OnDeserialization(object sender)
{
if (m_serializationInfo != null)
{
T[] values = (T[])m_serializationInfo.GetValue("Values", typeof(T[]));
Initialize(values.Length);
foreach (T value in values)
{
Add(value);
}
m_serializationInfo = null;
}
}
#endregion
#region Implementation of IEnumerable
public IEnumerator<T> GetEnumerator()
{
int startVersion = m_version;
int index = 1;
while (index < m_insertIndex)
{
if (startVersion != m_version)
{
throw new InvalidOperationException("Collection was modified during the enumeration");
}
if (!m_entries[index].Empty)
{
yield return m_entries[index].Value;
}
index++;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Nested
[StructLayout(LayoutKind.Sequential), Serializable]
private struct Entry
{
public ushort HashCode;
public ushort Next;
public T Value;
public bool Empty
{
get { return HashCode == InvalidIndex; }
}
public bool Is(int hash, T value, IEqualityComparer<T> comparer)
{
return HashCode == hash && comparer.Equals(Value, value);
}
}
public static class Invariants
{
public static void Validate(ShortHash<T> hash)
{
ValidateCount(hash);
ValidateHash(hash);
ValidateFreeList(hash);
}
private static void ValidateCount(ShortHash<T> hash)
{
int expectedCount = 0;
for (int i = 0; i < hash.m_entries.Length; i++)
{
if (!hash.m_entries[i].Empty)
{
expectedCount++;
}
}
Throw.InvalidProgramIf(expectedCount != hash.Count, "Invalid count. Expected {0}, was {1}", expectedCount, hash.Count);
}
private static void ValidateFreeList(ShortHash<T> hash)
{
int expectedFreeCount = 0;
for (int i = hash.m_nextFreeSlot; i != InvalidIndex; i = hash.m_entries[i].Next)
{
expectedFreeCount++;
}
Throw.InvalidProgramIf(expectedFreeCount != hash.m_freeCount, "Invalid free list");
}
private static void ValidateHash(ShortHash<T> hash)
{
int reachable = 0;
for (int i = 0; i < hash.m_table.Length; i++)
{
// Validate each bucket
for (ushort node = hash.m_table[i]; node != InvalidIndex; node = hash.m_entries[node].Next)
{
Entry entry = hash.m_entries[node];
ushort expectedHash;
unchecked
{
expectedHash = (ushort)((hash.m_comparer.GetSupportingNullHashCode(entry.Value) | NonZeroMask) & PositiveMask);
}
Throw.InvalidProgramIf(entry.HashCode != expectedHash, "Invalid hash");
int expectedBucket = entry.HashCode % hash.m_table.Length;
Throw.InvalidProgramIf(expectedBucket != i, "Invalid hash");
Throw.InvalidProgramIf(entry.Empty, "Invalid hash");
reachable++;
}
}
Throw.InvalidProgramIf(reachable != hash.Count, "Invalid entry list");
}
}
#endregion
public bool Add(T value, CollisionBehavior collisionBehavior, ref IVariableSizeHash<T> newHash)
{
if (Count == MaxCount)
{
StopEnumerations();
newHash = new FastHash<T>(Count + 1, m_comparer);
newHash.AddRange(this, ref newHash);
return newHash.Add(value, collisionBehavior, ref newHash);
}
return Insert(value, collisionBehavior);
}
public void AddRange(IEnumerable<T> values, ref IVariableSizeHash<T> newHash)
{
int valueCount = values.Count();
if (valueCount + Count > MaxCount)
{
StopEnumerations();
newHash = new FastHash<T>(Count + 1, m_comparer);
newHash.AddRange(this, ref newHash);
newHash.AddRange(values, ref newHash);
}
else
{
Capacity += valueCount;
foreach (var value in values)
{
Insert(value, CollisionBehavior.Throw);
}
}
}
IVariableSizeHash<T> IVariableSizeHash<T>.Clone()
{
return Clone();
}
}
}