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
{
///
/// Stores values using a hasing algorithm.
///
/// Should eventually replace Hash
[Serializable, ComVisible(false), DebuggerDisplay("Count = {Count}")]
public class FastHash : IHash, ISerializable, IDeserializationCallback, IVariableSizeHash
{
#region Constants
private const int GrowFactor = 2;
private const int InvalidIndex = 0;
public const int MaxCount = int.MaxValue - 2;
public const int MaxIndex = int.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 int[] m_table;
private readonly IEqualityComparer 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 int m_nextFreeSlot = InvalidIndex;
private int m_insertIndex = 1;
private int m_freeCount;
private int m_version;
[NonSerialized]
private SerializationInfo m_serializationInfo;
#endregion
#region Constructor
public FastHash()
: this(0, null)
{
}
public FastHash(IEqualityComparer comparer)
: this(0, comparer)
{
}
public FastHash(int capacity)
: this(capacity, null)
{
}
public FastHash(int capacity, IEqualityComparer comparer)
{
Throw.ArgumentOutOfRangeIf(capacity < 0, "capacity", "capacity must be positive");
Initialize(capacity);
m_comparer = comparer ?? EqualityComparer.Default;
}
protected FastHash(SerializationInfo serializationInfo, StreamingContext context)
{
m_insertIndex = 1;
m_freeCount = 0;
m_nextFreeSlot = InvalidIndex;
m_serializationInfo = serializationInfo;
m_comparer = (IEqualityComparer)m_serializationInfo.GetValue("Comparer", typeof(IEqualityComparer));
}
protected FastHash(FastHash other)
: this(other.Count, other.Comparer)
{
other.ForEach(Add);
}
#endregion
#region Properties
public IEqualityComparer Comparer
{
get
{
return m_comparer;
}
}
public int Count
{
get
{
return m_insertIndex - 1 - m_freeCount;
}
}
///
/// Get or set the count of items the hash can contain (cannot be less than the current size).
///
public int Capacity
{
get { return (int)(m_entries.Length - 1); }
set
{
if (value > Capacity)
{
ResizeHashTable(value);
ResizeEntries(value + 1);
}
}
}
///
/// 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.
///
public IEnumerable 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
///
/// Make sure current enumerations will crash on next MoveNext().
///
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 values)
{
Capacity += values.Count();
foreach (var value in values)
{
Insert(value, CollisionBehavior.Throw);
}
}
///
/// Adds a new item to the set. If the set already contains an item equal to
/// , that item is replaced with .
///
///
/// Equality between items is determined by the comparison instance or delegate used to create the set.
/// Adding an item takes approximately constant time, regardless of the number of items in the set.
/// The item to add to the set.
/// True if it added the value (if it wasn't present).
public bool TryAdd(T item)
{
return Insert(item, CollisionBehavior.Replace);
}
///
/// Adds a new item to the set. If the set already contains an item equal to
/// , that item is replaced with .
///
///
/// Equality between items is determined by the comparison instance or delegate used to create the set.
/// Adding an item takes approximately constant time, regardless of the number of items in the set.
/// The item to add to the set. If it was present, it will be the item in the hash.
/// True if it added the value (if it wasn't present).
public bool TryAdd(ref T item)
{
return Insert(ref item, CollisionBehavior.KeepActual);
}
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)
{
int hash;
unchecked
{
hash = (int)((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 lookup, out T removedValue)
{
int hash;
unchecked
{
hash = (int)((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)
{
int hash;
unchecked
{
hash = (int)((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 ContainsKey(ILookup lookup)
{
return FindEntry(lookup) != InvalidIndex;
}
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 lookup, out T result)
{
int index = FindEntry(lookup);
if (index != InvalidIndex)
{
result = m_entries[index].Value;
return true;
}
result = default(T);
return false;
}
public FastHash Clone()
{
return new FastHash(this);
}
IHash IHash.Clone()
{
return Clone();
}
///
/// Enumerates all the values that shares the hashcode that fits the lookup (useful for multidictionary).
///
public IEnumerable EnumerateValues(ILookup lookup)
{
Throw.IfNull(lookup, "lookup");
int hash;
unchecked
{
hash = (int)((lookup.HashCode | NonZeroMask) & PositiveMask);
}
for (var i = m_table[hash % m_table.Length]; i != InvalidIndex; i = m_entries[i].Next)
{
var value = m_entries[i].Value;
if (lookup.Equals(value))
{
yield return value;
}
}
}
#endregion
#region Private Methods
///
/// Not Using the Find with lookup to remove the overhead of creating an object for each search.
///
private int FindEntry(T value)
{
int hash;
unchecked
{
hash = (int)((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 lookup)
{
Throw.IfNull(lookup, "lookup");
int hash;
unchecked
{
hash = (int)((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 int[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)
{
return Insert(ref value, collisionBehavior);
}
private bool Insert(ref T value, CollisionBehavior collisionBehavior)
{
if (Count + 1 > m_table.Length)
{
ResizeHashTable(m_table.Length * GrowFactor);
}
int hash;
unchecked
{
hash = (int)((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++;
}
else if (collisionBehavior == CollisionBehavior.KeepActual)
{
value = m_entries[index].Value;
}
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 int InsertEntry(T value, int hash)
{
m_version++;
int 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 int[prime];
for (int 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 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 int HashCode;
public int Next;
public T Value;
public bool Empty
{
get { return HashCode == InvalidIndex; }
}
public bool Is(int hash, T value, IEqualityComparer comparer)
{
return HashCode == hash && comparer.Equals(Value, value);
}
}
public static class Invariants
{
public static void Validate(FastHash hash)
{
ValidateCount(hash);
ValidateHash(hash);
ValidateFreeList(hash);
}
private static void ValidateCount(FastHash 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(FastHash 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(FastHash hash)
{
int reachable = 0;
for (int i = 0; i < hash.m_table.Length; i++)
{
// Validate each bucket
for (int node = hash.m_table[i]; node != InvalidIndex; node = hash.m_entries[node].Next)
{
Entry entry = hash.m_entries[node];
int expectedHash;
unchecked
{
expectedHash = (int)((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
// IVariableHash is not implemented since for now, 2G items is plenty enough!
bool IVariableSizeHash.Add(T value, CollisionBehavior collisionBehavior, ref IVariableSizeHash newHash)
{
return Insert(value, collisionBehavior);
}
void IVariableSizeHash.AddRange(IEnumerable values, ref IVariableSizeHash newHash)
{
AddRange(values);
}
IVariableSizeHash IVariableSizeHash.Clone()
{
return Clone();
}
}
}