Generic Growable Array
// Copyright (c) Microsoft Corporation. All rights reserved. // This file is best viewed using outline mode (Ctrl-M Ctrl-O) // // This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in. // It is available from http://www.codeplex.com/hyperAddin // using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; namespace System.Collections.Generic { /// <summary> /// A cheap version of List(T). The idea is to make it as cheap as if you did it 'by hand' using an array and /// a int which represents the logical charCount. It is a struct to avoid an extra pointer dereference, so this /// is really meant to be embeded in other structures. /// /// Also made the Binary search is actually useful (by allowing the key to be something besides the element /// itself). /// </summary> public struct GrowableArray<T> { public GrowableArray(int initialSize) { array = new T[initialSize]; arrayLength = 0; } public T this[int index] { get { Debug.Assert((uint)index < (uint)arrayLength); return array[index]; } set { Debug.Assert((uint)index < (uint)arrayLength); array[index] = value; } } public int Count { get { return arrayLength; } set { if (value > arrayLength) { if (value <= array.Length) { // Null out the entries. for (int i = arrayLength; i < value; i++) array[i] = default(T); } else { T[] newArray = new T[value]; Array.Copy(array, newArray, array.Length); array = newArray; } } arrayLength = value; } } /// <summary> /// Add an item at the end of the array, growing as necessary. /// </summary> /// <param name="item"></param> public void Add(T item) { if (array == null || arrayLength >= array.Length) Realloc(); array[arrayLength++] = item; } /// <summary> /// Insert 'item' directly at 'index', shifting all items >= index up. 'index' can be code:Count in /// which case the item is appended to the end. Larger indexes are not allowed. /// </summary> public void Insert(int index, T item) { if ((uint)index > (uint)arrayLength) throw new IndexOutOfRangeException(); if (array == null || arrayLength >= array.Length) Realloc(); // Shift everything up to make room. for (int idx = arrayLength; index < idx; --idx) array[idx] = array[idx - 1]; // insert the element array[index] = item; arrayLength++; } public void RemoveRange(int index, int count) { if (count == 0) return; if (count < 0) throw new ArgumentException("count can't be negative"); if ((uint)index >= (uint)arrayLength) throw new IndexOutOfRangeException(); Debug.Assert(index + count <= arrayLength); // If you violate this it does not hurt // Shift everything down. for (int endIndex = index + count; endIndex < arrayLength; endIndex++) array[index++] = array[endIndex]; arrayLength = index; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append("GrowableArray(Count=").Append(Count).Append(", [").AppendLine(); for (int i = 0; i < Count; i++) sb.Append(" ").Append(this[i].ToString()).AppendLine(); sb.Append(" ])"); return sb.ToString(); } public delegate int Comparison<Key>(Key x, T elem); /// <summary> /// Sets 'index' to the the smallest index such that all elements with index > 'idx' are > key. If /// index does not match any elements a new element should always be placed AFTER index. Note that this /// means that index may be -1 if the new element belongs in the first position. /// /// return true if the return index matched exactly (success) /// </summary> public bool BinarySearch<Key>(Key key, out int index, Comparison<Key> comparison) { // binary search int low = 0; int high = arrayLength; int lastLowCompare = -1; // If this number == 0 we had a match. if (high > 0) { // The invarient in this loop is that // [0..low) <= key < [high..Count) for (; ; ) { int mid = (low + high) / 2; int compareResult = comparison(key, array[mid]); if (compareResult >= 0) // key >= array[mid], move low up { lastLowCompare = compareResult; // remember this result, as it indicates a sucessful match. if (mid == low) break; low = mid; } else // key < array[mid], move high down { high = mid; if (mid == low) break; } // Note that if compareResults == 0, we don't return the match eagerly because there could be // multiple elements that match. We want the match with the largest possible index, so we need // to continue the search until the valid range drops to 0 } } if (lastLowCompare < 0) // key < array[low], subtract 1 to indicate that new element goes BEFORE low. { Debug.Assert(low == 0); // can only happen if it is the first element --low; } index = low; Debug.Assert(index == -1 || comparison(key, array[index]) >= 0); // element smaller or equal to key Debug.Assert(index + 1 >= Count || comparison(key, array[index + 1]) < 0); // The next element is strictly bigger. Debug.Assert((lastLowCompare != 0) || (comparison(key, array[index]) == 0)); // If we say there is a match, there is. return (lastLowCompare == 0); } public void Sort(int index, int length, System.Comparison<T> comparison) { Debug.Assert(index + length <= arrayLength); if (length > 0) Array.Sort<T>(array, index, length, new FunctorComparer<T>(comparison)); } public void Sort(System.Comparison<T> comparison) { if (array != null) Array.Sort<T>(array, 0, arrayLength, new FunctorComparer<T>(comparison)); } /// <summary> /// Perform a linear search starting at 'startIndex'. If found return true and the index in 'index'. /// It is legal that 'startIndex' is greater than the charCount, in which case, the search returns false /// immediately. This allows a nice loop to find all items matching a pattern. /// </summary> public bool Search<Key>(Key key, int startIndex, Comparison<Key> compare, ref int index) { for (int i = startIndex; i < arrayLength; i++) { if (compare(key, array[i]) == 0) { index = i; return true; } } return false; } #region private private void Realloc() { if (array == null) { array = new T[16]; } else { int newLength = array.Length * 3 / 2 + 8; T[] newArray = new T[newLength]; Array.Copy(array, newArray, array.Length); array = newArray; } } T[] array; int arrayLength; #endregion #region TESTING // Unit testing. It is reasonable coverage, but concentrates on BinarySearch as that is the one that is // easy to get wrong. #if TESTING public static void TestGrowableArray() { GrowableArray<float> testArray = new GrowableArray<float>(); for (float i = 1.1F; i < 10; i += 2) { int successes = TestBinarySearch(testArray); Debug.Assert(successes == ((int)i) / 2); testArray.Add(i); } for (float i = 0.1F; i < 11; i += 2) { int index; bool result = testArray.BinarySearch(i, out index, delegate(float key, float elem) { return (int)key - (int)elem; }); Debug.Assert(!result); testArray.InsertAt(index + 1, i); } int lastSuccesses = TestBinarySearch(testArray); Debug.Assert(lastSuccesses == 11); for (float i = 0; i < 11; i += 1) { int index; bool result = testArray.BinarySearch(i, out index, delegate(float key, float elem) { return (int)key - (int)elem; }); Debug.Assert(result); testArray.InsertAt(index + 1, i); } lastSuccesses = TestBinarySearch(testArray); Debug.Assert(lastSuccesses == 11); // We always get the last one when the equality comparision allows multiple items to match. for (float i = 0; i < 11; i += 1) { int index; bool result = testArray.BinarySearch(i, out index, delegate(float key, float elem) { return (int)key - (int)elem; }); Debug.Assert(result); Debug.Assert(i == testArray[index]); } Console.WriteLine("Done"); } private static int TestBinarySearch(GrowableArray<float> testArray) { int successes = 0; for (int i = 0; i < 30; i++) { int index; if (testArray.BinarySearch(i, out index, delegate(float key, float elem) { return (int)key - (int)elem; })) { successes++; Debug.Assert((int)testArray[index] == i); } else Debug.Assert(index + 1 <= testArray.Count); } return successes; } #endif #endregion // This allows 'foreach' to work. public GrowableArrayEnumerator GetEnumerator() { return new GrowableArrayEnumerator(this); } public struct GrowableArrayEnumerator { public T Current { get { return array[cur]; } } public bool MoveNext() { cur++; return cur < end; } #region private internal GrowableArrayEnumerator(GrowableArray<T> growableArray) { cur = -1; end = growableArray.arrayLength; array = growableArray.array; } int cur; int end; T[] array; #endregion } } internal class FunctorComparer<T> : IComparer<T> { public FunctorComparer(Comparison<T> comparison) { this.comparison = comparison; } public int Compare(T x, T y) { return comparison(x, y); } private Comparison<T> comparison; }; }