You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
569 lines
18 KiB
569 lines
18 KiB
// Needed for NET40 |
|
#if !NET_4_6 |
|
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Threading; |
|
|
|
namespace LinqInternal.Collections.ThreadSafe |
|
{ |
|
/// <summary> |
|
/// Represent a thread-safe lock-free hash based dictionary. |
|
/// </summary> |
|
/// <typeparam name="T">The type of the value.</typeparam> |
|
[Serializable] |
|
internal class SafeSet<T> : IEnumerable<T>, ISet<T> |
|
{ |
|
private const int _defaultProbing = 1; |
|
private readonly IEqualityComparer<T> _comparer; |
|
private Bucket<T> _bucket; |
|
private int _probing; |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="SafeSet{T}" /> class. |
|
/// </summary> |
|
public SafeSet() |
|
: this(EqualityComparer<T>.Default, _defaultProbing) |
|
{ |
|
// Empty |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="SafeSet{T}" /> class. |
|
/// </summary> |
|
/// <param name="initialProbing">The number of steps in linear probing.</param> |
|
public SafeSet(int initialProbing) |
|
: this(EqualityComparer<T>.Default, initialProbing) |
|
{ |
|
// Empty |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="SafeSet{T}" /> class. |
|
/// </summary> |
|
/// <param name="comparer">The value comparer.</param> |
|
public SafeSet(IEqualityComparer<T> comparer) |
|
: this(comparer, _defaultProbing) |
|
{ |
|
// Empty |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="SafeSet{T}" /> class. |
|
/// </summary> |
|
/// <param name="comparer">The value comparer.</param> |
|
/// <param name="initialProbing">The number of steps in linear probing.</param> |
|
public SafeSet(IEqualityComparer<T> comparer, int initialProbing) |
|
{ |
|
_comparer = comparer ?? EqualityComparer<T>.Default; |
|
_bucket = new Bucket<T>(); |
|
_probing = initialProbing; |
|
} |
|
|
|
public IEqualityComparer<T> Comparer |
|
{ |
|
get { return _comparer; } |
|
} |
|
|
|
public int Count |
|
{ |
|
get { return _bucket.Count; } |
|
} |
|
|
|
bool ICollection<T>.IsReadOnly |
|
{ |
|
get { return false; } |
|
} |
|
|
|
public bool Add(T item) |
|
{ |
|
var hashCode = _comparer.GetHashCode(item); |
|
var attempts = 0; |
|
while (true) |
|
{ |
|
ExtendProbingIfNeeded(attempts); |
|
T found; |
|
if (_bucket.Insert(hashCode + attempts, item, out found)) |
|
{ |
|
return true; |
|
} |
|
if (_comparer.Equals(found, item)) |
|
{ |
|
return false; |
|
} |
|
attempts++; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Adds the specified value. |
|
/// </summary> |
|
/// <param name="value">The value.</param> |
|
/// <exception cref="System.ArgumentException">the value is already present</exception> |
|
public void AddNew(T value) |
|
{ |
|
var hashCode = _comparer.GetHashCode(value); |
|
var attempts = 0; |
|
while (true) |
|
{ |
|
ExtendProbingIfNeeded(attempts); |
|
T found; |
|
if (_bucket.Insert(hashCode + attempts, value, out found)) |
|
{ |
|
return; |
|
} |
|
if (_comparer.Equals(found, value)) |
|
{ |
|
throw new ArgumentException("the value is already present"); |
|
} |
|
attempts++; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Removes all the elements. |
|
/// </summary> |
|
public void Clear() |
|
{ |
|
_bucket = new Bucket<T>(); |
|
} |
|
|
|
/// <summary> |
|
/// Removes all the elements. |
|
/// </summary> |
|
/// <returns>Returns the removed pairs.</returns> |
|
public IEnumerable<T> ClearEnumerable() |
|
{ |
|
return Interlocked.Exchange(ref _bucket, _bucket = new Bucket<T>()); |
|
} |
|
|
|
/// <summary> |
|
/// Determines whether the specified value is contained. |
|
/// </summary> |
|
/// <param name="value">The value.</param> |
|
/// <returns> |
|
/// <c>true</c> if the specified value is contained; otherwise, <c>false</c>. |
|
/// </returns> |
|
public bool Contains(T value) |
|
{ |
|
var hashCode = _comparer.GetHashCode(value); |
|
for (var attempts = 0; attempts < _probing; attempts++) |
|
{ |
|
T found; |
|
if (_bucket.TryGet(hashCode + attempts, out found)) |
|
{ |
|
if (_comparer.Equals(found, value)) |
|
{ |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Determines whether the specified value is contained. |
|
/// </summary> |
|
/// <param name="hashCode">The hash code to look for.</param> |
|
/// <param name="check">The value predicate.</param> |
|
/// <returns> |
|
/// <c>true</c> if the specified value is contained; otherwise, <c>false</c>. |
|
/// </returns> |
|
public bool Contains(int hashCode, Predicate<T> check) |
|
{ |
|
if (check == null) |
|
{ |
|
throw new ArgumentNullException("check"); |
|
} |
|
for (var attempts = 0; attempts < _probing; attempts++) |
|
{ |
|
T found; |
|
if (_bucket.TryGet(hashCode + attempts, out found)) |
|
{ |
|
if (_comparer.GetHashCode(found) == hashCode && check(found)) |
|
{ |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Copies the items to a compatible one-dimensional array, starting at the specified index of the target array. |
|
/// </summary> |
|
/// <param name="array">The array.</param> |
|
/// <param name="arrayIndex">Index of the array.</param> |
|
/// <exception cref="System.ArgumentNullException">array</exception> |
|
/// <exception cref="System.ArgumentOutOfRangeException">arrayIndex;Non-negative number is required.</exception> |
|
/// <exception cref="System.ArgumentException">array;The array can not contain the number of elements.</exception> |
|
public void CopyTo(T[] array, int arrayIndex) |
|
{ |
|
_bucket.CopyTo(array, arrayIndex); |
|
} |
|
|
|
public void ExceptWith(IEnumerable<T> other) |
|
{ |
|
Extensions.ExceptWith(this, other); |
|
} |
|
|
|
/// <summary> |
|
/// Returns an <see cref="System.Collections.Generic.IEnumerator{T}" /> that allows to iterate through the collection. |
|
/// </summary> |
|
/// <returns> |
|
/// An <see cref="System.Collections.Generic.IEnumerator{T}" /> object that can be used to iterate through the collection. |
|
/// </returns> |
|
public IEnumerator<T> GetEnumerator() |
|
{ |
|
return _bucket.GetEnumerator(); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the pairs contained in this object. |
|
/// </summary> |
|
/// <returns>The pairs contained in this object</returns> |
|
public IList<T> GetValues() |
|
{ |
|
var result = new List<T>(_bucket.Count); |
|
foreach (var pair in _bucket) |
|
{ |
|
result.Add(pair); |
|
} |
|
return result; |
|
} |
|
|
|
public void IntersectWith(IEnumerable<T> other) |
|
{ |
|
Extensions.IntersectWith(this, other); |
|
} |
|
|
|
public bool IsProperSubsetOf(IEnumerable<T> other) |
|
{ |
|
return Extensions.IsProperSubsetOf(this, other); |
|
} |
|
|
|
public bool IsProperSupersetOf(IEnumerable<T> other) |
|
{ |
|
return Extensions.IsProperSupersetOf(this, other); |
|
} |
|
|
|
public bool IsSubsetOf(IEnumerable<T> other) |
|
{ |
|
return Extensions.IsSubsetOf(this, other); |
|
} |
|
|
|
public bool IsSupersetOf(IEnumerable<T> other) |
|
{ |
|
return Extensions.IsSupersetOf(this, other); |
|
} |
|
|
|
public bool Overlaps(IEnumerable<T> other) |
|
{ |
|
return Extensions.Overlaps(this, other); |
|
} |
|
|
|
/// <summary> |
|
/// Removes the specified value. |
|
/// </summary> |
|
/// <param name="value">The value.</param> |
|
/// <returns> |
|
/// <c>true</c> if the specified value was removed; otherwise, <c>false</c>. |
|
/// </returns> |
|
public bool Remove(T value) |
|
{ |
|
var hashCode = _comparer.GetHashCode(value); |
|
for (var attempts = 0; attempts < _probing; attempts++) |
|
{ |
|
var done = false; |
|
var result = _bucket.RemoveAt |
|
( |
|
hashCode + attempts, |
|
found => |
|
{ |
|
if (_comparer.Equals(found, value)) |
|
{ |
|
done = true; |
|
return true; |
|
} |
|
return false; |
|
} |
|
); |
|
if (done) |
|
{ |
|
return result; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Removes the specified value. |
|
/// </summary> |
|
/// <param name="value">The value.</param> |
|
/// <param name="previous">The found value that was removed.</param> |
|
/// <returns> |
|
/// <c>true</c> if the specified value was removed; otherwise, <c>false</c>. |
|
/// </returns> |
|
public bool Remove(T value, out T previous) |
|
{ |
|
var hashCode = _comparer.GetHashCode(value); |
|
for (var attempts = 0; attempts < _probing; attempts++) |
|
{ |
|
var done = false; |
|
var tmp = default(T); |
|
var result = _bucket.RemoveAt |
|
( |
|
hashCode + attempts, |
|
found => |
|
{ |
|
tmp = found; |
|
if (_comparer.Equals(found, value)) |
|
{ |
|
done = true; |
|
return true; |
|
} |
|
return false; |
|
} |
|
); |
|
if (done) |
|
{ |
|
previous = tmp; |
|
return result; |
|
} |
|
} |
|
previous = default(T); |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Removes a value by hash code and a value predicate. |
|
/// </summary> |
|
/// <param name="hashCode">The hash code to look for.</param> |
|
/// <param name="check">The value predicate.</param> |
|
/// <param name="value">The value.</param> |
|
/// <returns> |
|
/// <c>true</c> if the specified value was removed; otherwise, <c>false</c>. |
|
/// </returns> |
|
public bool Remove(int hashCode, Predicate<T> check, out T value) |
|
{ |
|
if (check == null) |
|
{ |
|
throw new ArgumentNullException("check"); |
|
} |
|
value = default(T); |
|
for (var attempts = 0; attempts < _probing; attempts++) |
|
{ |
|
var done = false; |
|
var previous = default(T); |
|
var result = _bucket.RemoveAt |
|
( |
|
hashCode + attempts, |
|
found => |
|
{ |
|
previous = found; |
|
if (_comparer.GetHashCode(found) == hashCode && check(found)) |
|
{ |
|
done = true; |
|
return true; |
|
} |
|
return false; |
|
} |
|
); |
|
if (done) |
|
{ |
|
value = previous; |
|
return result; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Removes the values where the predicate is satisfied. |
|
/// </summary> |
|
/// <param name="check">The predicate.</param> |
|
/// <returns> |
|
/// The number or removed values. |
|
/// </returns> |
|
/// <remarks> |
|
/// It is not guaranteed that all the values that satisfies the predicate will be removed. |
|
/// </remarks> |
|
public int RemoveWhere(Predicate<T> check) |
|
{ |
|
if (check == null) |
|
{ |
|
throw new ArgumentNullException("check"); |
|
} |
|
var matches = _bucket.Where(check); |
|
var count = 0; |
|
foreach (var value in matches) |
|
{ |
|
if (Remove(value)) |
|
{ |
|
count++; |
|
} |
|
} |
|
return count; |
|
} |
|
|
|
/// <summary> |
|
/// Removes the values where the predicate is satisfied. |
|
/// </summary> |
|
/// <param name="check">The predicate.</param> |
|
/// <returns> |
|
/// An <see cref="IEnumerable{TValue}" /> that allows to iterate over the removed values. |
|
/// </returns> |
|
/// <remarks> |
|
/// It is not guaranteed that all the values that satisfies the predicate will be removed. |
|
/// </remarks> |
|
public IEnumerable<T> RemoveWhereEnumerable(Predicate<T> check) |
|
{ |
|
if (check == null) |
|
{ |
|
throw new ArgumentNullException("check"); |
|
} |
|
var matches = _bucket.Where(check); |
|
foreach (var value in matches) |
|
{ |
|
if (Remove(value)) |
|
{ |
|
yield return value; |
|
} |
|
} |
|
} |
|
|
|
public bool SetEquals(IEnumerable<T> other) |
|
{ |
|
return Extensions.SetEquals(this, other); |
|
} |
|
|
|
public void SymmetricExceptWith(IEnumerable<T> other) |
|
{ |
|
Extensions.SymmetricExceptWith(this, other); |
|
} |
|
|
|
/// <summary> |
|
/// Tries to retrieve the value by hash code and value predicate. |
|
/// </summary> |
|
/// <param name="hashCode">The hash code to look for.</param> |
|
/// <param name="check">The value predicate.</param> |
|
/// <param name="value">The value.</param> |
|
/// <returns> |
|
/// <c>true</c> if the value was retrieved; otherwise, <c>false</c>. |
|
/// </returns> |
|
public bool TryGetValue(int hashCode, Predicate<T> check, out T value) |
|
{ |
|
if (check == null) |
|
{ |
|
throw new ArgumentNullException("check"); |
|
} |
|
value = default(T); |
|
for (var attempts = 0; attempts < _probing; attempts++) |
|
{ |
|
T found; |
|
if (_bucket.TryGet(hashCode + attempts, out found)) |
|
{ |
|
if (_comparer.GetHashCode(found) == hashCode && check(found)) |
|
{ |
|
value = found; |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
public void UnionWith(IEnumerable<T> other) |
|
{ |
|
Extensions.UnionWith(this, other); |
|
} |
|
|
|
/// <summary> |
|
/// Returns the values where the predicate is satisfied. |
|
/// </summary> |
|
/// <param name="check">The predicate.</param> |
|
/// <returns> |
|
/// An <see cref="IEnumerable{TValue}" /> that allows to iterate over the values. |
|
/// </returns> |
|
/// <remarks> |
|
/// It is not guaranteed that all the values that satisfies the predicate will be returned. |
|
/// </remarks> |
|
public IEnumerable<T> Where(Predicate<T> check) |
|
{ |
|
if (check == null) |
|
{ |
|
throw new ArgumentNullException("check"); |
|
} |
|
return _bucket.Where(check); |
|
} |
|
|
|
void ICollection<T>.Add(T item) |
|
{ |
|
AddNew(item); |
|
} |
|
|
|
IEnumerator IEnumerable.GetEnumerator() |
|
{ |
|
return GetEnumerator(); |
|
} |
|
|
|
/// <summary> |
|
/// Attempts to add the specified value. |
|
/// </summary> |
|
/// <param name="value">The value.</param> |
|
/// <param name="valueOverwriteCheck">The value predicate to approve overwriting.</param> |
|
/// <returns> |
|
/// <c>true</c> if the specified key and associated value were added; otherwise, <c>false</c>. |
|
/// </returns> |
|
internal bool TryAdd(T value, Predicate<T> valueOverwriteCheck) |
|
{ |
|
if (valueOverwriteCheck == null) |
|
{ |
|
throw new ArgumentNullException("valueOverwriteCheck"); |
|
} |
|
var hashCode = _comparer.GetHashCode(value); |
|
var attempts = 0; |
|
while (true) |
|
{ |
|
ExtendProbingIfNeeded(attempts); |
|
Predicate<T> check = found => |
|
{ |
|
if (_comparer.Equals(found, value)) |
|
{ |
|
// This is the item that has been stored with the key |
|
// Throw to abort overwrite |
|
throw new ArgumentException("The item has already been added"); |
|
} |
|
// This is not the value, overwrite? |
|
return valueOverwriteCheck(found); |
|
}; |
|
try |
|
{ |
|
bool isNew; |
|
// TryGetCheckSet will add if no item is found, otherwise it calls check |
|
if (_bucket.InsertOrUpdate(hashCode + attempts, value, check, out isNew)) |
|
{ |
|
// It added a new item |
|
return true; |
|
} |
|
} |
|
catch (ArgumentException) |
|
{ |
|
// An item with the same key has already been added |
|
return false; |
|
} |
|
attempts++; |
|
} |
|
} |
|
|
|
private void ExtendProbingIfNeeded(int attempts) |
|
{ |
|
var diff = attempts - _probing; |
|
if (diff > 0) |
|
{ |
|
Interlocked.Add(ref _probing, diff); |
|
} |
|
} |
|
} |
|
} |
|
#endif |