184 lines
No EOL
4.6 KiB
C#
184 lines
No EOL
4.6 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using JD.Collections.Details;
|
|
using JD.Kernel.StaticHelpers;
|
|
|
|
namespace JD.Collections
|
|
{
|
|
/// <summary>
|
|
/// A collection that allows multiple item to share the same key. Operations are in the order of clash count.
|
|
/// </summary>
|
|
public abstract class MultiKeyedCollection<TKey, TValue> : IEnumerable<TValue>
|
|
{
|
|
#region Variables
|
|
|
|
private readonly IEqualityComparer<TKey> m_keyEqualityComparer;
|
|
private readonly FastHash<TValue> m_hash;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
protected MultiKeyedCollection()
|
|
: this(EqualityComparer<TKey>.Default)
|
|
{
|
|
}
|
|
|
|
protected MultiKeyedCollection(IEqualityComparer<TKey> keyComparer)
|
|
{
|
|
Throw.IfNull(keyComparer, "keyComparer");
|
|
|
|
m_keyEqualityComparer = keyComparer;
|
|
m_hash = new FastHash<TValue>(new NeverEqualComparer(this));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public int Count
|
|
{
|
|
get { return m_hash.Count; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
public void Add(TValue value)
|
|
{
|
|
m_hash.Add(value);
|
|
}
|
|
|
|
public bool Remove(TValue value)
|
|
{
|
|
TValue dummy;
|
|
return Remove(value, out dummy);
|
|
}
|
|
|
|
public bool Remove(TValue value, out TValue removed)
|
|
{
|
|
return m_hash.Remove(new ValueLookup(value, this), out removed);
|
|
}
|
|
|
|
public IEnumerable<TValue> this[TKey key]
|
|
{
|
|
get { return m_hash.EnumerateValues(new KeyLookup(key, this)); }
|
|
}
|
|
|
|
private int GetHashCodeFor(TValue value)
|
|
{
|
|
return GetHashCode(GetKeyForItem(value));
|
|
}
|
|
|
|
protected abstract TKey GetKeyForItem(TValue value);
|
|
|
|
private bool KeyEquals(TKey a, TKey b)
|
|
{
|
|
return m_keyEqualityComparer.Equals(a, b);
|
|
}
|
|
|
|
private int GetHashCode(TKey key)
|
|
{
|
|
return m_keyEqualityComparer.GetHashCode(key);
|
|
}
|
|
|
|
protected virtual bool ValueEquals(TValue a, TValue b)
|
|
{
|
|
return Equals(a, b);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable
|
|
|
|
public IEnumerator<TValue> GetEnumerator()
|
|
{
|
|
return m_hash.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Nested
|
|
|
|
/// <summary>
|
|
/// When adding a value, we consider each value is not equal.
|
|
/// </summary>
|
|
private class NeverEqualComparer : IEqualityComparer<TValue>
|
|
{
|
|
private readonly MultiKeyedCollection<TKey, TValue> m_owner;
|
|
|
|
public NeverEqualComparer(MultiKeyedCollection<TKey, TValue> owner)
|
|
{
|
|
m_owner = owner;
|
|
}
|
|
|
|
public bool Equals(TValue x, TValue y)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public int GetHashCode(TValue obj)
|
|
{
|
|
return m_owner.GetHashCodeFor(obj);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When deleting a value, we delete the first that is equal to the object.
|
|
/// </summary>
|
|
private class ValueLookup : ILookup<TValue>
|
|
{
|
|
private readonly MultiKeyedCollection<TKey, TValue> m_owner;
|
|
private readonly TValue m_value;
|
|
|
|
public ValueLookup(TValue value, MultiKeyedCollection<TKey, TValue> owner)
|
|
{
|
|
m_value = value;
|
|
m_owner = owner;
|
|
}
|
|
|
|
public int HashCode
|
|
{
|
|
get { return m_owner.GetHashCodeFor(m_value); }
|
|
}
|
|
|
|
public bool Equals(TValue other)
|
|
{
|
|
return m_owner.ValueEquals(m_value, other);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When enumerating, enumerate all objects that shares the same key.
|
|
/// </summary>
|
|
private class KeyLookup : ILookup<TValue>
|
|
{
|
|
private readonly MultiKeyedCollection<TKey, TValue> m_owner;
|
|
private readonly TKey m_key;
|
|
|
|
public KeyLookup(TKey key, MultiKeyedCollection<TKey, TValue> owner)
|
|
{
|
|
m_owner = owner;
|
|
m_key = key;
|
|
}
|
|
|
|
public int HashCode
|
|
{
|
|
get { return m_owner.GetHashCode(m_key); }
|
|
}
|
|
|
|
public bool Equals(TValue other)
|
|
{
|
|
return m_owner.KeyEquals(m_key, m_owner.GetKeyForItem(other));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |