using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
namespace Cryville.Common.Collections.Specialized {
///
/// Represents a collection of keys and values. Identical to but much faster.
///
/// The type of the values in the dictionary.
[DebuggerTypeProxy(typeof(IntKeyedDictionaryDebugView<>))]
[DebuggerDisplay("Count = {Count}")]
public class IntKeyedDictionary : IDictionary, IDictionary, IReadOnlyDictionary {
private struct Entry {
public int next; // Index of next entry, -1 if last
public int key; // Key of entry
public T value; // Value of entry
}
private int[] buckets;
private Entry[] entries;
private int count;
private int version;
private int freeList;
private int freeCount;
private KeyCollection keys;
private ValueCollection values;
private Object _syncRoot;
public IntKeyedDictionary() : this(0) { }
public IntKeyedDictionary(int capacity) {
if (capacity < 0) throw new ArgumentOutOfRangeException("capacity");
if (capacity > 0) Initialize(capacity);
}
public IntKeyedDictionary(IDictionary dictionary) :
this(dictionary != null ? dictionary.Count : 0) {
if (dictionary == null) {
throw new ArgumentNullException("dictionary");
}
foreach (KeyValuePair pair in dictionary) {
Add(pair.Key, pair.Value);
}
}
public int Count {
get { return count - freeCount; }
}
public KeyCollection Keys {
get {
Contract.Ensures(Contract.Result() != null);
if (keys == null) keys = new KeyCollection(this);
return keys;
}
}
ICollection IDictionary.Keys {
get {
if (keys == null) keys = new KeyCollection(this);
return keys;
}
}
IEnumerable IReadOnlyDictionary.Keys {
get {
if (keys == null) keys = new KeyCollection(this);
return keys;
}
}
public ValueCollection Values {
get {
Contract.Ensures(Contract.Result() != null);
if (values == null) values = new ValueCollection(this);
return values;
}
}
ICollection IDictionary.Values {
get {
if (values == null) values = new ValueCollection(this);
return values;
}
}
IEnumerable IReadOnlyDictionary.Values {
get {
if (values == null) values = new ValueCollection(this);
return values;
}
}
public T this[int key] {
get {
int i = FindEntry(key);
if (i >= 0) return entries[i].value;
throw new KeyNotFoundException();
}
set {
Insert(key, value, false);
}
}
public void Add(int key, T value) {
Insert(key, value, true);
}
void ICollection>.Add(KeyValuePair keyValuePair) {
Add(keyValuePair.Key, keyValuePair.Value);
}
bool ICollection>.Contains(KeyValuePair keyValuePair) {
int i = FindEntry(keyValuePair.Key);
if (i >= 0) {
return true;
}
return false;
}
bool ICollection>.Remove(KeyValuePair keyValuePair) {
int i = FindEntry(keyValuePair.Key);
if (i >= 0) {
Remove(keyValuePair.Key);
return true;
}
return false;
}
public void Clear() {
if (count > 0) {
for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
Array.Clear(entries, 0, count);
freeList = -1;
count = 0;
freeCount = 0;
version++;
}
}
public bool ContainsKey(int key) {
return FindEntry(key) >= 0;
}
public bool ContainsValue(T value) {
if (value == null) {
for (int i = 0; i < count; i++) {
if (entries[i].key >= 0 && entries[i].value == null) return true;
}
}
else {
EqualityComparer c = EqualityComparer.Default;
for (int i = 0; i < count; i++) {
if (entries[i].key >= 0 && c.Equals(entries[i].value, value)) return true;
}
}
return false;
}
private void CopyTo(KeyValuePair[] array, int index) {
if (array == null) {
throw new ArgumentNullException("array");
}
if (index < 0 || index > array.Length) {
throw new ArgumentOutOfRangeException("index", "Non-negative number required.");
}
if (array.Length - index < Count) {
throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length.");
}
int count = this.count;
Entry[] entries = this.entries;
for (int i = 0; i < count; i++) {
if (entries[i].key >= 0) {
array[index++] = new KeyValuePair(entries[i].key, entries[i].value);
}
}
}
public Enumerator GetEnumerator() {
return new Enumerator(this, Enumerator.KeyValuePair);
}
IEnumerator> IEnumerable>.GetEnumerator() {
return new Enumerator(this, Enumerator.KeyValuePair);
}
private int FindEntry(int key) {
if (buckets != null) {
for (int i = buckets[key % buckets.Length]; i >= 0; i = entries[i].next) {
if (entries[i].key == key) return i;
}
}
return -1;
}
private void Initialize(int capacity) {
int size = HashHelpers.GetPrime(capacity);
buckets = new int[size];
for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
entries = new Entry[size];
freeList = -1;
}
private void Insert(int key, T value, bool add) {
if (buckets == null) Initialize(0);
int targetBucket = key % buckets.Length;
#if FEATURE_RANDOMIZED_STRING_HASHING
int collisionCount = 0;
#endif
for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {
if (entries[i].key == key) {
if (add) {
throw new ArgumentException("An item with the same key has already been added.");
}
entries[i].value = value;
version++;
return;
}
#if FEATURE_RANDOMIZED_STRING_HASHING
collisionCount++;
#endif
}
int index;
if (freeCount > 0) {
index = freeList;
freeList = entries[index].next;
freeCount--;
}
else {
if (count == entries.Length) {
Resize();
targetBucket = key % buckets.Length;
}
index = count;
count++;
}
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = value;
buckets[targetBucket] = index;
version++;
}
private void Resize() {
Resize(HashHelpers.ExpandPrime(count), false);
}
private void Resize(int newSize, bool forceNewHashCodes) {
Contract.Assert(newSize >= entries.Length);
int[] newBuckets = new int[newSize];
for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1;
Entry[] newEntries = new Entry[newSize];
Array.Copy(entries, 0, newEntries, 0, count);
if (forceNewHashCodes) {
for (int i = 0; i < count; i++) {
if (newEntries[i].key != -1) {
newEntries[i].key = newEntries[i].key;
}
}
}
for (int i = 0; i < count; i++) {
if (newEntries[i].key >= 0) {
int bucket = newEntries[i].key % newSize;
newEntries[i].next = newBuckets[bucket];
newBuckets[bucket] = i;
}
}
buckets = newBuckets;
entries = newEntries;
}
public bool Remove(int key) {
if (buckets != null) {
int bucket = key % buckets.Length;
int last = -1;
for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) {
if (entries[i].key == key) {
if (last < 0) {
buckets[bucket] = entries[i].next;
}
else {
entries[last].next = entries[i].next;
}
entries[i].key = -1;
entries[i].next = freeList;
entries[i].value = default(T);
freeList = i;
freeCount++;
version++;
return true;
}
}
}
return false;
}
public bool TryGetValue(int key, out T value) {
int i = FindEntry(key);
if (i >= 0) {
value = entries[i].value;
return true;
}
value = default(T);
return false;
}
// This is a convenience method for the internal callers that were converted from using Hashtable.
// Many were combining key doesn't exist and key exists but null value (for non-value types) checks.
// This allows them to continue getting that behavior with minimal code delta. This is basically
// TryGetValue without the out param
internal T GetValueOrDefault(int key) {
int i = FindEntry(key);
if (i >= 0) {
return entries[i].value;
}
return default(T);
}
bool ICollection>.IsReadOnly {
get { return false; }
}
void ICollection>.CopyTo(KeyValuePair[] array, int index) {
CopyTo(array, index);
}
void ICollection.CopyTo(Array array, int index) {
if (array == null) {
throw new ArgumentNullException("array");
}
if (array.Rank != 1) {
throw new ArgumentException("Only single dimensional arrays are supported for the requested action.");
}
if (array.GetLowerBound(0) != 0) {
throw new ArgumentException("The lower bound of target array must be zero.");
}
if (index < 0 || index > array.Length) {
throw new ArgumentOutOfRangeException("index", "Non-negative number required.");
}
if (array.Length - index < Count) {
throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length.");
}
KeyValuePair[] pairs = array as KeyValuePair[];
if (pairs != null) {
CopyTo(pairs, index);
}
else if (array is DictionaryEntry[]) {
DictionaryEntry[] dictEntryArray = array as DictionaryEntry[];
Entry[] entries = this.entries;
for (int i = 0; i < count; i++) {
if (entries[i].key >= 0) {
dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value);
}
}
}
else {
object[] objects = array as object[];
if (objects == null) {
throw new ArgumentException("Target array type is not compatible with the type of items in the collection.");
}
try {
int count = this.count;
Entry[] entries = this.entries;
for (int i = 0; i < count; i++) {
if (entries[i].key >= 0) {
objects[index++] = new KeyValuePair(entries[i].key, entries[i].value);
}
}
}
catch (ArrayTypeMismatchException) {
throw new ArgumentException("Target array type is not compatible with the type of items in the collection.");
}
}
}
IEnumerator IEnumerable.GetEnumerator() {
return new Enumerator(this, Enumerator.KeyValuePair);
}
bool ICollection.IsSynchronized {
get { return false; }
}
object ICollection.SyncRoot {
get {
if (_syncRoot == null) {
System.Threading.Interlocked.CompareExchange