/* Copyright (c) 2003-2006 Niels Kokholm and Peter Sestoft 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. */ #define IMPROVED_COLLECTION_HASHFUNCTION using System; using System.Diagnostics; using SCG = System.Collections.Generic; namespace C5 { /// /// A base class for implementing an IEnumerable<T> /// [Serializable] public abstract class EnumerableBase : SCG.IEnumerable { /// /// Create an enumerator for this collection. /// /// The enumerator public abstract SCG.IEnumerator GetEnumerator(); /// /// Count the number of items in an enumerable by enumeration /// /// The enumerable to count /// The size of the enumerable [Tested] protected static int countItems(SCG.IEnumerable items) { ICollectionValue jtems = items as ICollectionValue; if (jtems != null) return jtems.Count; int count = 0; using (SCG.IEnumerator e = items.GetEnumerator()) while (e.MoveNext()) count++; return count; } #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } /// /// Base class for classes implementing ICollectionValue[T] /// [Serializable] public abstract class CollectionValueBase : EnumerableBase, ICollectionValue, IShowable { #region Event handling EventBlock eventBlock; /// /// /// /// public virtual EventTypeEnum ListenableEvents { get { return 0; } } /// /// A flag bitmap of the events currently subscribed to by this collection. /// /// public virtual EventTypeEnum ActiveEvents { get { return eventBlock == null ? 0 : eventBlock.events; } } private void checkWillListen(EventTypeEnum eventType) { if ((ListenableEvents & eventType) == 0) throw new UnlistenableEventException(); } /// /// The change event. Will be raised for every change operation on the collection. /// public virtual event CollectionChangedHandler CollectionChanged { add { checkWillListen(EventTypeEnum.Changed); (eventBlock ?? (eventBlock = new EventBlock())).CollectionChanged += value; } remove { checkWillListen(EventTypeEnum.Changed); if (eventBlock != null) { eventBlock.CollectionChanged -= value; if (eventBlock.events == 0) eventBlock = null; } } } /// /// Fire the CollectionChanged event /// protected virtual void raiseCollectionChanged() { if (eventBlock != null) eventBlock.raiseCollectionChanged(this); } /// /// The clear event. Will be raised for every Clear operation on the collection. /// public virtual event CollectionClearedHandler CollectionCleared { add { checkWillListen(EventTypeEnum.Cleared); (eventBlock ?? (eventBlock = new EventBlock())).CollectionCleared += value; } remove { checkWillListen(EventTypeEnum.Cleared); if (eventBlock != null) { eventBlock.CollectionCleared -= value; if (eventBlock.events == 0) eventBlock = null; } } } /// /// Fire the CollectionCleared event /// protected virtual void raiseCollectionCleared(bool full, int count) { if (eventBlock != null) eventBlock.raiseCollectionCleared(this, full, count); } /// /// Fire the CollectionCleared event /// protected virtual void raiseCollectionCleared(bool full, int count, int? offset) { if (eventBlock != null) eventBlock.raiseCollectionCleared(this, full, count, offset); } /// /// The item added event. Will be raised for every individual addition to the collection. /// public virtual event ItemsAddedHandler ItemsAdded { add { checkWillListen(EventTypeEnum.Added); (eventBlock ?? (eventBlock = new EventBlock())).ItemsAdded += value; } remove { checkWillListen(EventTypeEnum.Added); if (eventBlock != null) { eventBlock.ItemsAdded -= value; if (eventBlock.events == 0) eventBlock = null; } } } /// /// Fire the ItemsAdded event /// /// The item that was added /// protected virtual void raiseItemsAdded(T item, int count) { if (eventBlock != null) eventBlock.raiseItemsAdded(this, item, count); } /// /// The item removed event. Will be raised for every individual removal from the collection. /// public virtual event ItemsRemovedHandler ItemsRemoved { add { checkWillListen(EventTypeEnum.Removed); (eventBlock ?? (eventBlock = new EventBlock())).ItemsRemoved += value; } remove { checkWillListen(EventTypeEnum.Removed); if (eventBlock != null) { eventBlock.ItemsRemoved -= value; if (eventBlock.events == 0) eventBlock = null; } } } /// /// Fire the ItemsRemoved event /// /// The item that was removed /// protected virtual void raiseItemsRemoved(T item, int count) { if (eventBlock != null) eventBlock.raiseItemsRemoved(this, item, count); } /// /// The item added event. Will be raised for every individual addition to the collection. /// public virtual event ItemInsertedHandler ItemInserted { add { checkWillListen(EventTypeEnum.Inserted); (eventBlock ?? (eventBlock = new EventBlock())).ItemInserted += value; } remove { checkWillListen(EventTypeEnum.Inserted); if (eventBlock != null) { eventBlock.ItemInserted -= value; if (eventBlock.events == 0) eventBlock = null; } } } /// /// Fire the ItemInserted event /// /// The item that was added /// protected virtual void raiseItemInserted(T item, int index) { if (eventBlock != null) eventBlock.raiseItemInserted(this, item, index); } /// /// The item removed event. Will be raised for every individual removal from the collection. /// public virtual event ItemRemovedAtHandler ItemRemovedAt { add { checkWillListen(EventTypeEnum.RemovedAt); (eventBlock ?? (eventBlock = new EventBlock())).ItemRemovedAt += value; } remove { checkWillListen(EventTypeEnum.RemovedAt); if (eventBlock != null) { eventBlock.ItemRemovedAt -= value; if (eventBlock.events == 0) eventBlock = null; } } } /// /// Fire the ItemRemovedAt event /// /// The item that was removed /// protected virtual void raiseItemRemovedAt(T item, int index) { if (eventBlock != null) eventBlock.raiseItemRemovedAt(this, item, index); } #region Event support for IList /// /// /// /// /// /// protected virtual void raiseForSetThis(int index, T value, T item) { if (ActiveEvents != 0) { raiseItemsRemoved(item, 1); raiseItemRemovedAt(item, index); raiseItemsAdded(value, 1); raiseItemInserted(value, index); raiseCollectionChanged(); } } /// /// /// /// /// protected virtual void raiseForInsert(int i, T item) { if (ActiveEvents != 0) { raiseItemInserted(item, i); raiseItemsAdded(item, 1); raiseCollectionChanged(); } } /// /// /// /// protected void raiseForRemove(T item) { if (ActiveEvents != 0) { raiseItemsRemoved(item, 1); raiseCollectionChanged(); } } /// /// /// /// /// protected void raiseForRemove(T item, int count) { if (ActiveEvents != 0) { raiseItemsRemoved(item, count); raiseCollectionChanged(); } } /// /// /// /// /// protected void raiseForRemoveAt(int index, T item) { if (ActiveEvents != 0) { raiseItemRemovedAt(item, index); raiseItemsRemoved(item, 1); raiseCollectionChanged(); } } #endregion #region Event Support for ICollection /// /// /// /// /// protected virtual void raiseForUpdate(T newitem, T olditem) { if (ActiveEvents != 0) { raiseItemsRemoved(olditem, 1); raiseItemsAdded(newitem, 1); raiseCollectionChanged(); } } /// /// /// /// /// /// protected virtual void raiseForUpdate(T newitem, T olditem, int count) { if (ActiveEvents != 0) { raiseItemsRemoved(olditem, count); raiseItemsAdded(newitem, count); raiseCollectionChanged(); } } /// /// /// /// protected virtual void raiseForAdd(T item) { if (ActiveEvents != 0) { raiseItemsAdded(item, 1); raiseCollectionChanged(); } } /// /// /// /// protected virtual void raiseForRemoveAll(ICollectionValue wasRemoved) { if ((ActiveEvents & EventTypeEnum.Removed) != 0) foreach (T item in wasRemoved) raiseItemsRemoved(item, 1); if (wasRemoved != null && wasRemoved.Count > 0) raiseCollectionChanged(); } /// /// /// protected class RaiseForRemoveAllHandler { CollectionValueBase collection; CircularQueue wasRemoved; bool wasChanged = false; /// /// /// /// public RaiseForRemoveAllHandler(CollectionValueBase collection) { this.collection = collection; mustFireRemoved = (collection.ActiveEvents & EventTypeEnum.Removed) != 0; MustFire = (collection.ActiveEvents & (EventTypeEnum.Removed | EventTypeEnum.Changed)) != 0; } bool mustFireRemoved; /// /// /// public readonly bool MustFire; /// /// /// /// public void Remove(T item) { if (mustFireRemoved) { if (wasRemoved == null) wasRemoved = new CircularQueue(); wasRemoved.Enqueue(item); } if (!wasChanged) wasChanged = true; } /// /// /// public void Raise() { if (wasRemoved != null) foreach (T item in wasRemoved) collection.raiseItemsRemoved(item, 1); if (wasChanged) collection.raiseCollectionChanged(); } } #endregion #endregion /// /// Check if collection is empty. /// /// True if empty public abstract bool IsEmpty { get;} /// /// The number of items in this collection. /// /// public abstract int Count { get;} /// /// The value is symbolic indicating the type of asymptotic complexity /// in terms of the size of this collection (worst-case or amortized as /// relevant). /// /// A characterization of the speed of the /// Count property in this collection. public abstract Speed CountSpeed { get; } /// /// Copy the items of this collection to part of an array. /// /// if index /// is not a valid index /// into the array (i.e. negative or greater than the size of the array) /// or the array does not have room for the items. /// The array to copy to. /// The starting index. [Tested] public virtual void CopyTo(T[] array, int index) { if (index < 0 || index + Count > array.Length) throw new ArgumentOutOfRangeException(); foreach (T item in this) array[index++] = item; } /// /// Create an array with the items of this collection (in the same order as an /// enumerator would output them). /// /// The array //[Tested] public virtual T[] ToArray() { T[] res = new T[Count]; int i = 0; foreach (T item in this) res[i++] = item; return res; } /// /// Apply an single argument action, to this enumerable /// /// The action delegate [Tested] public virtual void Apply(Act action) { foreach (T item in this) action(item); } /// /// Check if there exists an item that satisfies a /// specific predicate in this collection. /// /// A delegate /// ( with R = bool) /// defining the predicate /// True if such an item exists [Tested] public virtual bool Exists(Fun predicate) { foreach (T item in this) if (predicate(item)) return true; return false; } /// /// Check if there exists an item that satisfies a /// specific predicate in this collection and return the first one in enumeration order. /// /// A delegate /// ( with R == bool) defining the predicate /// /// True is such an item exists public virtual bool Find(Fun predicate, out T item) { foreach (T jtem in this) if (predicate(jtem)) { item = jtem; return true; } item = default(T); return false; } /// /// Check if all items in this collection satisfies a specific predicate. /// /// A delegate /// ( with R = bool) /// defining the predicate /// True if all items satisfies the predicate [Tested] public virtual bool All(Fun predicate) { foreach (T item in this) if (!predicate(item)) return false; return true; } /// /// Create an enumerable, enumerating the items of this collection that satisfies /// a certain condition. /// /// A delegate /// ( with R = bool) /// defining the predicate /// The filtered enumerable public virtual SCG.IEnumerable Filter(Fun predicate) { foreach (T item in this) if (predicate(item)) yield return item; } /// /// Choose some item of this collection. /// /// if collection is empty. /// public abstract T Choose(); /// /// Create an enumerator for this collection. /// /// The enumerator public override abstract SCG.IEnumerator GetEnumerator(); #region IShowable Members /// /// /// /// /// /// /// public virtual bool Show(System.Text.StringBuilder stringbuilder, ref int rest, IFormatProvider formatProvider) { return Showing.ShowCollectionValue(this, stringbuilder, ref rest, formatProvider); } #endregion #region IFormattable Members /// /// /// /// /// /// public virtual string ToString(string format, IFormatProvider formatProvider) { return Showing.ShowString(this, format, formatProvider); } #endregion /// /// /// /// public override string ToString() { return ToString(null, null); } } /// /// /// /// public abstract class DirectedCollectionValueBase : CollectionValueBase, IDirectedCollectionValue { /// /// Forwards if same, else Backwards /// /// The enumeration direction relative to the original collection. public virtual EnumerationDirection Direction { [Tested]get { return EnumerationDirection.Forwards; } } /// /// /// /// public abstract IDirectedCollectionValue Backwards(); IDirectedEnumerable IDirectedEnumerable.Backwards() { return this.Backwards(); } /// /// Check if there exists an item that satisfies a /// specific predicate in this collection and return the first one in enumeration order. /// /// A delegate /// ( with R == bool) defining the predicate /// /// True is such an item exists public virtual bool FindLast(Fun predicate, out T item) { foreach (T jtem in Backwards()) if (predicate(jtem)) { item = jtem; return true; } item = default(T); return false; } } /// /// Base class (abstract) for ICollection implementations. /// [Serializable] public abstract class CollectionBase : CollectionValueBase { #region Fields /// /// The underlying field of the ReadOnly property /// protected bool isReadOnlyBase = false; /// /// The current stamp value /// protected int stamp; /// /// The number of items in the collection /// protected int size; /// /// The item equalityComparer of the collection /// protected readonly SCG.IEqualityComparer itemequalityComparer; int iUnSequencedHashCode, iUnSequencedHashCodeStamp = -1; #endregion /// /// /// /// protected CollectionBase(SCG.IEqualityComparer itemequalityComparer) { if (itemequalityComparer == null) throw new NullReferenceException("Item EqualityComparer cannot be null."); this.itemequalityComparer = itemequalityComparer; } #region Util /// /// Utility method for range checking. /// /// if the start or count is negative or /// if the range does not fit within collection size. /// start of range /// size of range [Tested] protected void checkRange(int start, int count) { if (start < 0 || count < 0 || start + count > size) throw new ArgumentOutOfRangeException(); } /// /// Compute the unsequenced hash code of a collection /// /// The collection to compute hash code for /// The item equalityComparer /// The hash code [Tested] public static int ComputeHashCode(ICollectionValue items, SCG.IEqualityComparer itemequalityComparer) { int h = 0; #if IMPROVED_COLLECTION_HASHFUNCTION //But still heuristic: //Note: the three odd factors should really be random, //but there will be a problem with serialization/deserialization! //Two products is too few foreach (T item in items) { uint h1 = (uint)itemequalityComparer.GetHashCode(item); h += (int)((h1 * 1529784657 + 1) ^ (h1 * 2912831877) ^ (h1 * 1118771817 + 2)); } return h; /* The pairs (-1657792980, -1570288808) and (1862883298, -272461342) gives the same unsequenced hashcode with this hashfunction. The pair was found with code like HashDictionary set = new HashDictionary(); Random rnd = new C5Random(12345); while (true) { int[] a = new int[2]; a[0] = rnd.Next(); a[1] = rnd.Next(); int h = unsequencedhashcode(a); int[] b = a; if (set.FindOrAdd(h, ref b)) { Console.WriteLine("Code {5}, Pair ({1},{2}) number {0} matched other pair ({3},{4})", set.Count, a[0], a[1], b[0], b[1], h); } } */ #else foreach (T item in items) h ^= itemequalityComparer.GetHashCode(item); return (items.Count << 16) + h; #endif } static Type isortedtype = typeof(ISorted); /// /// Examine if collection1 and collection2 are equal as unsequenced collections /// using the specified item equalityComparer (assumed compatible with the two collections). /// /// The first collection /// The second collection /// The item equalityComparer to use for comparison /// True if equal [Tested] public static bool StaticEquals(ICollection collection1, ICollection collection2, SCG.IEqualityComparer itemequalityComparer) { if (object.ReferenceEquals(collection1, collection2)) return true; // bug20070227: if (collection1 == null || collection2 == null) return false; if (collection1.Count != collection2.Count) return false; //This way we might run through both enumerations twice, but //probably not (if the hash codes are good) //TODO: check equal equalityComparers, at least here! if (collection1.GetUnsequencedHashCode() != collection2.GetUnsequencedHashCode()) return false; //TODO: move this to the sorted implementation classes? //Really depends on speed of InstanceOfType: we could save a cast { ISorted stit, stat; if ((stit = collection1 as ISorted) != null && (stat = collection2 as ISorted) != null && stit.Comparer == stat.Comparer) { using (SCG.IEnumerator dat = collection2.GetEnumerator(), dit = collection1.GetEnumerator()) { while (dit.MoveNext()) { dat.MoveNext(); if (!itemequalityComparer.Equals(dit.Current, dat.Current)) return false; } return true; } } } if (!collection1.AllowsDuplicates && (collection2.AllowsDuplicates || collection2.ContainsSpeed >= collection1.ContainsSpeed)) { foreach (T x in collection1) if (!collection2.Contains(x)) return false; } else if (!collection2.AllowsDuplicates) { foreach (T x in collection2) if (!collection1.Contains(x)) return false; } // Now tit.AllowsDuplicates && tat.AllowsDuplicates else if (collection1.DuplicatesByCounting && collection2.DuplicatesByCounting) { foreach (T item in collection2) if (collection1.ContainsCount(item) != collection2.ContainsCount(item)) return false; } else { // To avoid an O(n^2) algorithm, we make an aux hashtable to hold the count of items // bug20101103: HashDictionary dict = new HashDictionary(); HashDictionary dict = new HashDictionary(itemequalityComparer); foreach (T item in collection2) { int count = 1; if (dict.FindOrAdd(item, ref count)) dict[item] = count + 1; } foreach (T item in collection1) { int count; if (dict.Find(item, out count) && count > 0) dict[item] = count - 1; else return false; } return true; } return true; } /// /// Get the unsequenced collection hash code of this collection: from the cached /// value if present and up to date, else (re)compute. /// /// The hash code public virtual int GetUnsequencedHashCode() { if (iUnSequencedHashCodeStamp == stamp) return iUnSequencedHashCode; iUnSequencedHashCode = ComputeHashCode(this, itemequalityComparer); iUnSequencedHashCodeStamp = stamp; return iUnSequencedHashCode; } /// /// Check if the contents of otherCollection is equal to the contents of this /// in the unsequenced sense. Uses the item equality comparer of this collection /// /// The collection to compare to. /// True if equal public virtual bool UnsequencedEquals(ICollection otherCollection) { return otherCollection != null && StaticEquals((ICollection)this, otherCollection, itemequalityComparer); } /// /// Check if the collection has been modified since a specified time, expressed as a stamp value. /// /// if this collection has been updated /// since a target time /// The stamp identifying the target time protected virtual void modifycheck(int thestamp) { if (this.stamp != thestamp) throw new CollectionModifiedException(); } /// /// Check if it is valid to perform update operations, and if so increment stamp. /// /// If colection is read-only protected virtual void updatecheck() { if (isReadOnlyBase) throw new ReadOnlyCollectionException(); stamp++; } #endregion #region ICollection members /// /// /// /// True if this collection is read only [Tested] public virtual bool IsReadOnly { [Tested]get { return isReadOnlyBase; } } #endregion #region ICollectionValue members /// /// /// /// The size of this collection [Tested] public override int Count { [Tested]get { return size; } } /// /// The value is symbolic indicating the type of asymptotic complexity /// in terms of the size of this collection (worst-case or amortized as /// relevant). /// /// A characterization of the speed of the /// Count property in this collection. public override Speed CountSpeed { get { return Speed.Constant; } } #endregion #region IExtensible members /// /// /// /// public virtual SCG.IEqualityComparer EqualityComparer { get { return itemequalityComparer; } } /// /// /// /// True if this collection is empty [Tested] public override bool IsEmpty { [Tested]get { return size == 0; } } #endregion #region IEnumerable Members /// /// Create an enumerator for this collection. /// /// The enumerator public override abstract SCG.IEnumerator GetEnumerator(); #endregion } /// /// /// /// [Serializable] public abstract class DirectedCollectionBase : CollectionBase, IDirectedCollectionValue { /// /// /// /// protected DirectedCollectionBase(SCG.IEqualityComparer itemequalityComparer) : base(itemequalityComparer) { } /// /// Forwards if same, else Backwards /// /// The enumeration direction relative to the original collection. public virtual EnumerationDirection Direction { [Tested]get { return EnumerationDirection.Forwards; } } /// /// /// /// public abstract IDirectedCollectionValue Backwards(); IDirectedEnumerable IDirectedEnumerable.Backwards() { return this.Backwards(); } /// /// Check if there exists an item that satisfies a /// specific predicate in this collection and return the first one in enumeration order. /// /// A delegate /// ( with R == bool) defining the predicate /// /// True is such an item exists public virtual bool FindLast(Fun predicate, out T item) { foreach (T jtem in Backwards()) if (predicate(jtem)) { item = jtem; return true; } item = default(T); return false; } } /// /// Base class (abstract) for sequenced collection implementations. /// [Serializable] public abstract class SequencedBase : DirectedCollectionBase, IDirectedCollectionValue { #region Fields int iSequencedHashCode, iSequencedHashCodeStamp = -1; #endregion /// /// /// /// protected SequencedBase(SCG.IEqualityComparer itemequalityComparer) : base(itemequalityComparer) { } #region Util //TODO: make random for release const int HASHFACTOR = 31; /// /// Compute the unsequenced hash code of a collection /// /// The collection to compute hash code for /// The item equalityComparer /// The hash code [Tested] public static int ComputeHashCode(ISequenced items, SCG.IEqualityComparer itemequalityComparer) { //NOTE: It must be possible to devise a much stronger combined hashcode, //but unfortunately, it has to be universal. OR we could use a (strong) //family and initialise its parameter randomly at load time of this class! //(We would not want to have yet a flag to check for invalidation?!) //NBNBNB: the current hashcode has the very bad property that items with hashcode 0 // is ignored. int iIndexedHashCode = 0; foreach (T item in items) iIndexedHashCode = iIndexedHashCode * HASHFACTOR + itemequalityComparer.GetHashCode(item); return iIndexedHashCode; } /// /// Examine if tit and tat are equal as sequenced collections /// using the specified item equalityComparer (assumed compatible with the two collections). /// /// The first collection /// The second collection /// The item equalityComparer to use for comparison /// True if equal [Tested] public static bool StaticEquals(ISequenced collection1, ISequenced collection2, SCG.IEqualityComparer itemequalityComparer) { if (object.ReferenceEquals(collection1, collection2)) return true; if (collection1.Count != collection2.Count) return false; //This way we might run through both enumerations twice, but //probably not (if the hash codes are good) if (collection1.GetSequencedHashCode() != collection2.GetSequencedHashCode()) return false; using (SCG.IEnumerator dat = collection2.GetEnumerator(), dit = collection1.GetEnumerator()) { while (dit.MoveNext()) { dat.MoveNext(); if (!itemequalityComparer.Equals(dit.Current, dat.Current)) return false; } } return true; } /// /// Get the sequenced collection hash code of this collection: from the cached /// value if present and up to date, else (re)compute. /// /// The hash code public virtual int GetSequencedHashCode() { if (iSequencedHashCodeStamp == stamp) return iSequencedHashCode; iSequencedHashCode = ComputeHashCode((ISequenced)this, itemequalityComparer); iSequencedHashCodeStamp = stamp; return iSequencedHashCode; } /// /// Check if the contents of that is equal to the contents of this /// in the sequenced sense. Using the item equalityComparer of this collection. /// /// The collection to compare to. /// True if equal public virtual bool SequencedEquals(ISequenced otherCollection) { return StaticEquals((ISequenced)this, otherCollection, itemequalityComparer); } #endregion /// /// Create an enumerator for this collection. /// /// The enumerator public override abstract SCG.IEnumerator GetEnumerator(); /// /// Forwards if same, else Backwards /// /// The enumeration direction relative to the original collection. [Tested] public override EnumerationDirection Direction { [Tested]get { return EnumerationDirection.Forwards; } } /// /// Check if there exists an item that satisfies a /// specific predicate in this collection and return the index of the first one. /// /// A delegate /// ( with R == bool) defining the predicate /// the index, if found, a negative value else public int FindIndex(Fun predicate) { int index = 0; foreach (T item in this) { if (predicate(item)) return index; index++; } return -1; } /// /// Check if there exists an item that satisfies a /// specific predicate in this collection and return the index of the last one. /// /// A delegate /// ( with R == bool) defining the predicate /// the index, if found, a negative value else public int FindLastIndex(Fun predicate) { int index = Count - 1; foreach (T item in Backwards()) { if (predicate(item)) return index; index--; } return -1; } } /// /// Base class for collection classes of dynamic array type implementations. /// [Serializable] public abstract class ArrayBase : SequencedBase { #region Fields /// /// The actual internal array container. Will be extended on demand. /// protected T[] array; /// /// The offset into the internal array container of the first item. The offset is 0 for a /// base dynamic array and may be positive for an updatable view into a base dynamic array. /// protected int offset; #endregion #region Util /// /// Double the size of the internal array. /// protected virtual void expand() { expand(2 * array.Length, size); } /// /// Expand the internal array container. /// /// The new size of the internal array - /// will be rounded upwards to a power of 2. /// The (new) size of the (base) collection. protected virtual void expand(int newcapacity, int newsize) { Debug.Assert(newcapacity >= newsize); int newlength = array.Length; while (newlength < newcapacity) newlength *= 2; T[] newarray = new T[newlength]; Array.Copy(array, newarray, newsize); array = newarray; } /// /// Insert an item at a specific index, moving items to the right /// upwards and expanding the array if necessary. /// /// The index at which to insert. /// The item to insert. protected virtual void insert(int i, T item) { if (size == array.Length) expand(); if (i < size) Array.Copy(array, i, array, i + 1, size - i); array[i] = item; size++; } #endregion #region Constructors /// /// Create an empty ArrayBase object. /// /// The initial capacity of the internal array container. /// Will be rounded upwards to the nearest power of 2 greater than or equal to 8. /// The item equalityComparer to use, primarily for item equality protected ArrayBase(int capacity, SCG.IEqualityComparer itemequalityComparer) : base(itemequalityComparer) { int newlength = 8; while (newlength < capacity) newlength *= 2; array = new T[newlength]; } #endregion #region IIndexed members /// /// /// If the arguments does not describe a /// valid range in the indexed collection, cf. . /// The directed collection of items in a specific index interval. /// The low index of the interval (inclusive). /// The size of the range. [Tested] public virtual IDirectedCollectionValue this[int start, int count] { [Tested] get { checkRange(start, count); return new Range(this, start, count, true); } } #endregion #region IEditableCollection members /// /// Remove all items and reset size of internal array container. /// [Tested] public virtual void Clear() { updatecheck(); array = new T[8]; size = 0; } /// /// Create an array containing (copies) of the items of this collection in enumeration order. /// /// The new array [Tested] public override T[] ToArray() { T[] res = new T[size]; Array.Copy(array, offset, res, 0, size); return res; } /// /// Perform an internal consistency (invariant) test on the array base. /// /// True if test succeeds. [Tested] public virtual bool Check() { bool retval = true; if (size > array.Length) { Console.WriteLine("Bad size ({0}) > array.Length ({1})", size, array.Length); return false; } for (int i = 0; i < size; i++) { if ((object)(array[i]) == null) { Console.WriteLine("Bad element: null at index {0}", i); return false; } } return retval; } #endregion #region IDirectedCollection Members /// /// Create a directed collection with the same contents as this one, but /// opposite enumeration sequence. /// /// The mirrored collection. [Tested] public override IDirectedCollectionValue Backwards() { return this[0, size].Backwards(); } #endregion /// /// Choose some item of this collection. The result is the last item in the internal array, /// making it efficient to remove. /// /// if collection is empty. /// public override T Choose() { if (size > 0) return array[size - 1]; throw new NoSuchItemException(); } #region IEnumerable Members /// /// Create an enumerator for this array based collection. /// /// The enumerator [Tested] public override SCG.IEnumerator GetEnumerator() { int thestamp = stamp, theend = size + offset, thestart = offset; for (int i = thestart; i < theend; i++) { modifycheck(thestamp); yield return array[i]; } } #endregion #region Range nested class /// /// A helper class for defining results of interval queries on array based collections. /// protected class Range : DirectedCollectionValueBase, IDirectedCollectionValue { int start, count, delta, stamp; ArrayBase thebase; internal Range(ArrayBase thebase, int start, int count, bool forwards) { this.thebase = thebase; stamp = thebase.stamp; delta = forwards ? 1 : -1; this.start = start + thebase.offset; this.count = count; } /// /// /// /// if underlying collection has been modified. /// True if this collection is empty. public override bool IsEmpty { get { thebase.modifycheck(stamp); return count == 0; } } /// /// /// /// if underlying collection has been modified. /// The number of items in the range [Tested] public override int Count { [Tested]get { thebase.modifycheck(stamp); return count; } } /// /// The value is symbolic indicating the type of asymptotic complexity /// in terms of the size of this collection (worst-case or amortized as /// relevant). /// /// A characterization of the speed of the /// if underlying collection has been modified. /// Count property in this collection. public override Speed CountSpeed { get { thebase.modifycheck(stamp); return Speed.Constant; } } /// /// Choose some item of this collection. /// /// if underlying collection has been modified. /// if range is empty. /// public override T Choose() { thebase.modifycheck(stamp); if (count == 0) throw new NoSuchItemException(); return thebase.array[start]; } /// /// Create an enumerator for this range of an array based collection. /// /// if underlying collection has been modified. /// The enumerator [Tested] public override SCG.IEnumerator GetEnumerator() { for (int i = 0; i < count; i++) { thebase.modifycheck(stamp); yield return thebase.array[start + delta * i]; } } /// /// Create a araay collection range with the same contents as this one, but /// opposite enumeration sequence. /// /// if underlying collection has been modified. /// The mirrored collection. [Tested] public override IDirectedCollectionValue Backwards() { thebase.modifycheck(stamp); Range res = (Range)MemberwiseClone(); res.delta = -delta; res.start = start + (count - 1) * delta; return res; } IDirectedEnumerable C5.IDirectedEnumerable.Backwards() { return Backwards(); } /// /// Forwards if same, else Backwards /// /// if underlying collection has been modified. /// The enumeration direction relative to the original collection. [Tested] public override EnumerationDirection Direction { [Tested] get { thebase.modifycheck(stamp); return delta > 0 ? EnumerationDirection.Forwards : EnumerationDirection.Backwards; } } } #endregion } }