// // System.Collections.Generic.SortedList.cs // // Author: // Sergey Chaban (serge@wildwestsoftware.com) // Duncan Mak (duncan@ximian.com) // Herve Poussineau (hpoussineau@fr.st // Zoltan Varga (vargaz@gmail.com) // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Globalization; using System.Runtime.InteropServices; using System.Diagnostics; namespace System.Collections.Generic { /// /// Represents a collection of associated keys and values /// that are sorted by the keys and are accessible by key /// and by index. /// [Serializable] [ComVisible(false)] [DebuggerDisplay ("Count={Count}")] [DebuggerTypeProxy (typeof (CollectionDebuggerView<,>))] public class SortedList : IDictionary, IDictionary, ICollection, ICollection>, IEnumerable>, IEnumerable { private readonly static int INITIAL_SIZE = 16; private enum EnumeratorMode : int { KEY_MODE = 0, VALUE_MODE, ENTRY_MODE } private int inUse; private int modificationCount; private KeyValuePair[] table; private IComparer comparer; private int defaultCapacity; // // Constructors // public SortedList () : this (INITIAL_SIZE, null) { } public SortedList (int capacity) : this (capacity, null) { } public SortedList (int capacity, IComparer comparer) { if (capacity < 0) throw new ArgumentOutOfRangeException ("initialCapacity"); if (capacity == 0) defaultCapacity = 0; else defaultCapacity = INITIAL_SIZE; Init (comparer, capacity, true); } public SortedList (IComparer comparer) : this (INITIAL_SIZE, comparer) { } public SortedList (IDictionary dictionary) : this (dictionary, null) { } public SortedList (IDictionary dictionary, IComparer comparer) { if (dictionary == null) throw new ArgumentNullException ("dictionary"); Init (comparer, dictionary.Count, true); foreach (KeyValuePair kvp in dictionary) Add (kvp.Key, kvp.Value); } // // Properties // // ICollection public int Count { get { return inUse; } } bool ICollection.IsSynchronized { get { return false; } } Object ICollection.SyncRoot { get { return this; } } // IDictionary bool IDictionary.IsFixedSize { get { return false; } } bool IDictionary.IsReadOnly { get { return false; } } public TValue this [TKey key] { get { if (key == null) throw new ArgumentNullException("key"); int i = Find (key); if (i >= 0) return table [i].Value; else throw new KeyNotFoundException (); } set { if (key == null) throw new ArgumentNullException("key"); PutImpl (key, value, true); } } object IDictionary.this [object key] { get { if (!(key is TKey)) return null; else return this [(TKey)key]; } set { this [ToKey (key)] = ToValue (value); } } public int Capacity { get { return table.Length; } set { int current = this.table.Length; if (inUse > value) { throw new ArgumentOutOfRangeException("capacity too small"); } else if (value == 0) { // return to default size KeyValuePair [] newTable = new KeyValuePair [defaultCapacity]; Array.Copy (table, newTable, inUse); this.table = newTable; } else if (value > inUse) { KeyValuePair [] newTable = new KeyValuePair [value]; Array.Copy (table, newTable, inUse); this.table = newTable; } else if (value > current) { KeyValuePair [] newTable = new KeyValuePair [value]; Array.Copy (table, newTable, current); this.table = newTable; } } } public IList Keys { get { return new ListKeys (this); } } public IList Values { get { return new ListValues (this); } } ICollection IDictionary.Keys { get { return new ListKeys (this); } } ICollection IDictionary.Values { get { return new ListValues (this); } } ICollection IDictionary.Keys { get { return Keys; } } ICollection IDictionary.Values { get { return Values; } } public IComparer Comparer { get { return comparer; } } bool ICollection>.IsReadOnly { get { return false; } } // // Public instance methods. // public void Add (TKey key, TValue value) { if (key == null) throw new ArgumentNullException ("key"); PutImpl (key, value, false); } public bool ContainsKey (TKey key) { if (key == null) throw new ArgumentNullException ("key"); return (Find (key) >= 0); } public IEnumerator> GetEnumerator () { for (int i = 0; i < inUse; i ++) { KeyValuePair current = this.table [i]; yield return new KeyValuePair (current.Key, current.Value); } } public bool Remove (TKey key) { if (key == null) throw new ArgumentNullException ("key"); int i = IndexOfKey (key); if (i >= 0) { RemoveAt (i); return true; } else return false; } // ICollection> void ICollection>.Clear () { Clear (); } public void Clear () { Array.Clear (table, 0, table.Length); inUse = 0; modificationCount++; } void ICollection>.CopyTo (KeyValuePair[] array, int arrayIndex) { if (Count == 0) return; if (null == array) throw new ArgumentNullException(); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(); if (arrayIndex >= array.Length) throw new ArgumentNullException("arrayIndex is greater than or equal to array.Length"); if (Count > (array.Length - arrayIndex)) throw new ArgumentNullException("Not enough space in array from arrayIndex to end of array"); int i = arrayIndex; foreach (KeyValuePair pair in this) array [i++] = pair; } void ICollection>.Add (KeyValuePair keyValuePair) { Add (keyValuePair.Key, keyValuePair.Value); } bool ICollection>.Contains (KeyValuePair keyValuePair) { int i = Find (keyValuePair.Key); if (i >= 0) return Comparer>.Default.Compare (table [i], keyValuePair) == 0; else return false; } bool ICollection>.Remove (KeyValuePair keyValuePair) { int i = Find (keyValuePair.Key); if (i >= 0 && (Comparer>.Default.Compare (table [i], keyValuePair) == 0)) { RemoveAt (i); return true; } else return false; } // IEnumerable> IEnumerator> IEnumerable>.GetEnumerator () { for (int i = 0; i < inUse; i ++) { KeyValuePair current = this.table [i]; yield return new KeyValuePair (current.Key, current.Value); } } // IEnumerable IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } // IDictionary void IDictionary.Add (object key, object value) { PutImpl (ToKey (key), ToValue (value), false); } bool IDictionary.Contains (object key) { if (null == key) throw new ArgumentNullException(); if (!(key is TKey)) return false; return (Find ((TKey)key) >= 0); } IDictionaryEnumerator IDictionary.GetEnumerator () { return new Enumerator (this, EnumeratorMode.ENTRY_MODE); } void IDictionary.Remove (object key) { if (null == key) throw new ArgumentNullException ("key"); if (!(key is TKey)) return; int i = IndexOfKey ((TKey)key); if (i >= 0) RemoveAt (i); } // ICollection void ICollection.CopyTo (Array array, int arrayIndex) { if (Count == 0) return; if (null == array) throw new ArgumentNullException(); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(); if (array.Rank > 1) throw new ArgumentException("array is multi-dimensional"); if (arrayIndex >= array.Length) throw new ArgumentNullException("arrayIndex is greater than or equal to array.Length"); if (Count > (array.Length - arrayIndex)) throw new ArgumentNullException("Not enough space in array from arrayIndex to end of array"); IEnumerator> it = GetEnumerator (); int i = arrayIndex; while (it.MoveNext ()) { array.SetValue (it.Current, i++); } } // // SortedList // public void RemoveAt (int index) { KeyValuePair [] table = this.table; int cnt = Count; if (index >= 0 && index < cnt) { if (index != cnt - 1) { Array.Copy (table, index+1, table, index, cnt-1-index); } else { table [index] = default (KeyValuePair ); } --inUse; ++modificationCount; } else { throw new ArgumentOutOfRangeException("index out of range"); } } public int IndexOfKey (TKey key) { if (key == null) throw new ArgumentNullException ("key"); int indx = Find (key); return (indx | (indx >> 31)); } public int IndexOfValue (TValue value) { if (inUse == 0) return -1; for (int i = 0; i < inUse; i ++) { KeyValuePair current = this.table [i]; if (Equals (value, current.Value)) return i; } return -1; } public bool ContainsValue (TValue value) { return IndexOfValue (value) >= 0; } public void TrimExcess () { if (inUse < table.Length * 0.9) Capacity = inUse; } public bool TryGetValue (TKey key, out TValue value) { if (key == null) throw new ArgumentNullException("key"); int i = Find (key); if (i >= 0) { value = table [i].Value; return true; } else { value = default (TValue); return false; } } // // Private methods // private void EnsureCapacity (int n, int free) { KeyValuePair [] table = this.table; KeyValuePair [] newTable = null; int cap = Capacity; bool gap = (free >=0 && free < Count); if (n > cap) { newTable = new KeyValuePair [n << 1]; } if (newTable != null) { if (gap) { int copyLen = free; if (copyLen > 0) { Array.Copy (table, 0, newTable, 0, copyLen); } copyLen = Count - free; if (copyLen > 0) { Array.Copy (table, free, newTable, free+1, copyLen); } } else { // Just a resizing, copy the entire table. Array.Copy (table, newTable, Count); } this.table = newTable; } else if (gap) { Array.Copy (table, free, table, free+1, Count - free); } } private void PutImpl (TKey key, TValue value, bool overwrite) { if (key == null) throw new ArgumentNullException ("null key"); KeyValuePair [] table = this.table; int freeIndx = Find (key); if (freeIndx >= 0) { if (!overwrite) throw new ArgumentException("element already exists"); table [freeIndx] = new KeyValuePair (key, value); ++modificationCount; return; } freeIndx = ~freeIndx; if (freeIndx > Capacity + 1) throw new Exception ("SortedList::internal error ("+key+", "+value+") at ["+freeIndx+"]"); EnsureCapacity (Count+1, freeIndx); table = this.table; table [freeIndx] = new KeyValuePair (key, value); ++inUse; ++modificationCount; } private void Init (IComparer comparer, int capacity, bool forceSize) { if (comparer == null) comparer = Comparer.Default; this.comparer = comparer; if (!forceSize && (capacity < defaultCapacity)) capacity = defaultCapacity; this.table = new KeyValuePair [capacity]; this.inUse = 0; this.modificationCount = 0; } private void CopyToArray (Array arr, int i, EnumeratorMode mode) { if (arr == null) throw new ArgumentNullException ("arr"); if (i < 0 || i + this.Count > arr.Length) throw new ArgumentOutOfRangeException ("i"); IEnumerator it = new Enumerator (this, mode); while (it.MoveNext ()) { arr.SetValue (it.Current, i++); } } private int Compare (TKey a, TKey b) { try { return comparer.Compare (a, b); } catch (Exception ex) { throw new InvalidOperationException ("Failed to compare two elements.", ex); } } private int Find (TKey key) { KeyValuePair [] table = this.table; int len = Count; if (len == 0) return ~0; int left = 0; int right = len-1; while (left <= right) { int guess = left + ((right - left) >> 1); int cmp = Compare (table[guess].Key, key); if (cmp == 0) return guess; if (cmp < 0) left = guess+1; else right = guess-1; } return ~left; } private TKey ToKey (object key) { if (key == null) throw new ArgumentNullException ("key"); if (!(key is TKey)) throw new ArgumentException ("The value \"" + key + "\" isn't of type \"" + typeof (TKey) + "\" and can't be used in this generic collection.", "key"); return (TKey)key; } private TValue ToValue (object value) { if (!(value is TValue)) throw new ArgumentException ("The value \"" + value + "\" isn't of type \"" + typeof (TValue) + "\" and can't be used in this generic collection.", "value"); return (TValue)value; } internal TKey KeyAt (int index) { if (index >= 0 && index < Count) return table [index].Key; else throw new ArgumentOutOfRangeException("Index out of range"); } internal TValue ValueAt (int index) { if (index >= 0 && index < Count) return table [index].Value; else throw new ArgumentOutOfRangeException("Index out of range"); } // // Inner classes // private sealed class Enumerator : ICloneable, IDictionaryEnumerator, IEnumerator { private SortedListhost; private int stamp; private int pos; private int size; private EnumeratorMode mode; private object currentKey; private object currentValue; bool invalid = false; private readonly static string xstr = "SortedList.Enumerator: snapshot out of sync."; public Enumerator (SortedListhost, EnumeratorMode mode) { this.host = host; stamp = host.modificationCount; size = host.Count; this.mode = mode; Reset (); } public Enumerator (SortedListhost) : this (host, EnumeratorMode.ENTRY_MODE) { } public void Reset () { if (host.modificationCount != stamp || invalid) throw new InvalidOperationException (xstr); pos = -1; currentKey = null; currentValue = null; } public bool MoveNext () { if (host.modificationCount != stamp || invalid) throw new InvalidOperationException (xstr); KeyValuePair [] table = host.table; if (++pos < size) { KeyValuePair entry = table [pos]; currentKey = entry.Key; currentValue = entry.Value; return true; } currentKey = null; currentValue = null; return false; } public DictionaryEntry Entry { get { if (invalid || pos >= size || pos == -1) throw new InvalidOperationException (xstr); return new DictionaryEntry (currentKey, currentValue); } } public Object Key { get { if (invalid || pos >= size || pos == -1) throw new InvalidOperationException (xstr); return currentKey; } } public Object Value { get { if (invalid || pos >= size || pos == -1) throw new InvalidOperationException (xstr); return currentValue; } } public Object Current { get { if (invalid || pos >= size || pos == -1) throw new InvalidOperationException (xstr); switch (mode) { case EnumeratorMode.KEY_MODE: return currentKey; case EnumeratorMode.VALUE_MODE: return currentValue; case EnumeratorMode.ENTRY_MODE: return this.Entry; default: throw new NotSupportedException (mode + " is not a supported mode."); } } } // ICloneable public object Clone () { Enumerator e = new Enumerator (host, mode); e.stamp = stamp; e.pos = pos; e.size = size; e.currentKey = currentKey; e.currentValue = currentValue; e.invalid = invalid; return e; } } [Serializable] struct KeyEnumerator : IEnumerator , IDisposable { const int NOT_STARTED = -2; // this MUST be -1, because we depend on it in move next. // we just decr the size, so, 0 - 1 == FINISHED const int FINISHED = -1; SortedList l; int idx; int ver; internal KeyEnumerator (SortedList l) { this.l = l; idx = NOT_STARTED; ver = l.modificationCount; } public void Dispose () { idx = NOT_STARTED; } public bool MoveNext () { if (ver != l.modificationCount) throw new InvalidOperationException ("Collection was modified after the enumerator was instantiated."); if (idx == NOT_STARTED) idx = l.Count; return idx != FINISHED && -- idx != FINISHED; } public TKey Current { get { if (idx < 0) throw new InvalidOperationException (); return l.KeyAt (l.Count - 1 - idx); } } void IEnumerator.Reset () { if (ver != l.modificationCount) throw new InvalidOperationException ("Collection was modified after the enumerator was instantiated."); idx = NOT_STARTED; } object IEnumerator.Current { get { return Current; } } } [Serializable] struct ValueEnumerator : IEnumerator , IDisposable { const int NOT_STARTED = -2; // this MUST be -1, because we depend on it in move next. // we just decr the size, so, 0 - 1 == FINISHED const int FINISHED = -1; SortedList l; int idx; int ver; internal ValueEnumerator (SortedList l) { this.l = l; idx = NOT_STARTED; ver = l.modificationCount; } public void Dispose () { idx = NOT_STARTED; } public bool MoveNext () { if (ver != l.modificationCount) throw new InvalidOperationException ("Collection was modified after the enumerator was instantiated."); if (idx == NOT_STARTED) idx = l.Count; return idx != FINISHED && -- idx != FINISHED; } public TValue Current { get { if (idx < 0) throw new InvalidOperationException (); return l.ValueAt (l.Count - 1 - idx); } } void IEnumerator.Reset () { if (ver != l.modificationCount) throw new InvalidOperationException ("Collection was modified after the enumerator was instantiated."); idx = NOT_STARTED; } object IEnumerator.Current { get { return Current; } } } private class ListKeys : IList, ICollection, IEnumerable { private SortedList host; public ListKeys (SortedList host) { if (host == null) throw new ArgumentNullException (); this.host = host; } // ICollection public virtual void Add (TKey item) { throw new NotSupportedException(); } public virtual bool Remove (TKey key) { throw new NotSupportedException (); } public virtual void Clear () { throw new NotSupportedException(); } public virtual void CopyTo (TKey[] array, int arrayIndex) { if (host.Count == 0) return; if (array == null) throw new ArgumentNullException ("array"); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(); if (arrayIndex >= array.Length) throw new ArgumentOutOfRangeException ("arrayIndex is greater than or equal to array.Length"); if (Count > (array.Length - arrayIndex)) throw new ArgumentOutOfRangeException("Not enough space in array from arrayIndex to end of array"); int j = arrayIndex; for (int i = 0; i < Count; ++i) array [j ++] = host.KeyAt (i); } public virtual bool Contains (TKey item) { return host.IndexOfKey (item) > -1; } // // IList // public virtual int IndexOf (TKey item) { return host.IndexOfKey (item); } public virtual void Insert (int index, TKey item) { throw new NotSupportedException (); } public virtual void RemoveAt (int index) { throw new NotSupportedException (); } public virtual TKey this [int index] { get { return host.KeyAt (index); } set { throw new NotSupportedException("attempt to modify a key"); } } // // IEnumerable // public virtual IEnumerator GetEnumerator () { /* We couldn't use yield as it does not support Reset () */ return new KeyEnumerator (host); } // // ICollection // public virtual int Count { get { return host.Count; } } public virtual bool IsSynchronized { get { return ((ICollection)host).IsSynchronized; } } public virtual bool IsReadOnly { get { return true; } } public virtual Object SyncRoot { get { return ((ICollection)host).SyncRoot; } } public virtual void CopyTo (Array array, int arrayIndex) { host.CopyToArray (array, arrayIndex, EnumeratorMode.KEY_MODE); } // // IEnumerable // IEnumerator IEnumerable.GetEnumerator () { for (int i = 0; i < host.Count; ++i) yield return host.KeyAt (i); } } private class ListValues : IList, ICollection, IEnumerable { private SortedListhost; public ListValues (SortedListhost) { if (host == null) throw new ArgumentNullException (); this.host = host; } // ICollection public virtual void Add (TValue item) { throw new NotSupportedException(); } public virtual bool Remove (TValue value) { throw new NotSupportedException (); } public virtual void Clear () { throw new NotSupportedException(); } public virtual void CopyTo (TValue[] array, int arrayIndex) { if (host.Count == 0) return; if (array == null) throw new ArgumentNullException ("array"); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(); if (arrayIndex >= array.Length) throw new ArgumentOutOfRangeException ("arrayIndex is greater than or equal to array.Length"); if (Count > (array.Length - arrayIndex)) throw new ArgumentOutOfRangeException("Not enough space in array from arrayIndex to end of array"); int j = arrayIndex; for (int i = 0; i < Count; ++i) array [j ++] = host.ValueAt (i); } public virtual bool Contains (TValue item) { return host.IndexOfValue (item) > -1; } // // IList // public virtual int IndexOf (TValue item) { return host.IndexOfValue (item); } public virtual void Insert (int index, TValue item) { throw new NotSupportedException (); } public virtual void RemoveAt (int index) { throw new NotSupportedException (); } public virtual TValue this [int index] { get { return host.ValueAt (index); } set { throw new NotSupportedException("attempt to modify a key"); } } // // IEnumerable // public virtual IEnumerator GetEnumerator () { /* We couldn't use yield as it does not support Reset () */ return new ValueEnumerator (host); } // // ICollection // public virtual int Count { get { return host.Count; } } public virtual bool IsSynchronized { get { return ((ICollection)host).IsSynchronized; } } public virtual bool IsReadOnly { get { return true; } } public virtual Object SyncRoot { get { return ((ICollection)host).SyncRoot; } } public virtual void CopyTo (Array array, int arrayIndex) { host.CopyToArray (array, arrayIndex, EnumeratorMode.VALUE_MODE); } // // IEnumerable // IEnumerator IEnumerable.GetEnumerator () { for (int i = 0; i < host.Count; ++i) yield return host.ValueAt (i); } } } // SortedList } // System.Collections.Generic