/Utilities/Collections/AutoSortList.cs
# · C# · 668 lines · 389 code · 39 blank · 240 comment · 23 complexity · 8144e08acce65d5d90d9d83938017d93 MD5 · raw file
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using Delta.Utilities.Helpers;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Collections
- {
- /// <summary>
- /// AutoSortArrayList extends the ArrayList to manage a list that is
- /// always sorted.
- /// </summary>
- /// <remarks>
- /// Details: AutoSortArrayList is basically an ArrayList which sorts
- /// itself whenever we add or remove items. The list does not use a key to
- /// identify each instance.
- /// The .NET framework offers similar classes, each with shortcomings.
- /// * ArrayList - Can sort any type of object but cannot automatically add
- /// instances into the correct sorted position.
- /// * SortedList - Can keep any type of object in a sorted order. However,
- /// it demands a key for every instance. If you don't manage your objects
- /// by name, this won't work.
- /// * StringsCollection - Can keep a list of strings without the requirement
- /// of a key. However, it can't keep them sorted.
- /// AutoSortArrayList is based on ArrayList and overrides any method that
- /// dictates order, such as Add(), Insert(), and AddRange(). It overrides
- /// methods that lookup data so that a binary search is used for speed.
- /// It overrides methods that allow the user to control the location of
- /// inserted data and throws InvalidOperationExceptions.
- ///
- /// AutoSortArrayList can be used with any class that you want to keep
- /// ordered. It determines order with an object that implements the
- /// IComparer interface. If you are collecting primative types like int
- /// and double, the default IComparer methodology is used. If you are
- /// collecting strings and need case insensitivity, .NET provides
- /// System.Collections.CaseInsensitiveComparer.
- ///
- /// Here is an overview of the key properties of AutoSortArrayList:
- /// * AutoSort - when true, Add() will insert in a sorted order.
- /// Defaults to true.
- /// * Comparer - use this to supply a custom IComparer. Defaults to null.
- /// * AreDuplicatesAllowed - when true, Add() will permit duplicate instances
- /// (as determined by BinarySearch() finding an exact match in the list.
- /// Defaults to false.
- /// There are several constructors, designed to override these property's
- /// defaults.
- /// </remarks>
- /// <typeparam name="T">Data type</typeparam>
- public class AutoSortList<T> : IList<T>, ICollection<T>, IEnumerable<T>
- {
- #region AutoSort (Public)
- /// <summary>
- /// AutoSort indicates if auto sorting is on.
- /// When true, Add() and AddRange() will insert the object in the
- /// position provided by BinarySearch. A number of methods will raise
- /// InvalidOperationException when true: Insert(), InsertRange(), and
- /// Reverse(). When false, all methods follow use their ancestor'str
- /// functionality. If set to false and you add items, when you set it
- /// back to true, Add() and AddRange() can incorrectly position data
- /// as a binary search is used to position data. So if switching from
- /// false to true, call Sort().
- /// </summary>
- /// <typeparam name="T">T</typeparam>
- public bool AutoSort
- {
- get
- {
- return autoSort;
- }
- }
- #endregion
-
- #region Comparer (Public)
- /// <summary>
- /// Comparer provides an IComparer interface for use with Sort() and
- /// BinarySearch(). By default, these methods use the IComparer methods
- /// of the objects you insert into the list. For primitive values, this
- /// works well. For strings, you may consider System.Collections.
- /// CaseInsensitiveComparer. But for custom objects, you'll need to help
- /// with an IComparer.
- /// Defaults to null.
- /// </summary>
- /// <typeparam name="T">T</typeparam>
- public IComparer<T> Comparer
- {
- get
- {
- return comparer;
- }
- set
- {
- comparer = value;
- Sort();
- }
- }
- #endregion
-
- #region AreDuplicatesAllowed (Public)
- /// <summary>
- /// AreDuplicatesAllowed determines if a duplicate is allowed into the list.
- /// This only applies with AutoSort is true.
- /// If Add() detects a duplicate when this is false, an
- /// InvalidOperationException is thrown.
- /// Defaults to false.
- /// </summary>
- /// <typeparam name="T">T</typeparam>
- public bool AreDuplicatesAllowed
- {
- get
- {
- return duplicatesAllowed;
- }
- }
- #endregion
-
- #region Item (Public)
- /// <summary>
- /// This
- /// </summary>
- /// <param name="index">Index</param>
- /// <returns>T</returns>
- public T this[int index]
- {
- get
- {
- return data[index];
- }
- set
- {
- data[index] = value;
- }
- }
- #endregion
-
- #region Count (Public)
- /// <summary>
- /// Count
- /// </summary>
- /// <returns>Int</returns>
- public int Count
- {
- get
- {
- return data.Count;
- }
- }
- #endregion
-
- #region IsReadOnly (Public)
- /// <summary>
- /// Is read only
- /// </summary>
- /// <returns>
- /// True if the list is readonly. Will always return false, this feature is
- /// not supported by AutoSortList).
- /// </returns>
- public bool IsReadOnly
- {
- get
- {
- return false;
- }
- }
- #endregion
-
- #region Protected
-
- #region autoSort (Protected)
- /// <summary>
- /// Auto sorting on?
- /// </summary>
- /// <typeparam name="T">T</typeparam>
- protected bool autoSort = true;
- #endregion
-
- #region comparer (Protected)
- /// <summary>
- /// Comparer for sort.
- /// </summary>
- /// <typeparam name="T">T</typeparam>
- protected IComparer<T> comparer;
- #endregion
-
- #region duplicatesAllowed (Protected)
- /// <summary>
- /// Duplicates allowed?
- /// </summary>
- /// <typeparam name="T">T</typeparam>
- protected bool duplicatesAllowed;
- #endregion
-
- #endregion
-
- #region Private
-
- #region data (Private)
- /// <summary>
- /// Using List<T>, we can't derive from it to override the methods.
- /// </summary>
- /// <typeparam name="T">T</typeparam>
- private readonly List<T> data = new List<T>();
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create auto sort list
- /// </summary>
- public AutoSortList()
- {
- }
-
- /// <summary>
- /// Create auto sort list
- /// </summary>
- /// <param name="setAutoSort">Auto sort</param>
- public AutoSortList(bool setAutoSort)
- {
- autoSort = setAutoSort;
- }
-
- /// <summary>
- /// Create auto sort list
- /// </summary>
- /// <param name="setAutoSort">Auto sort</param>
- /// <param name="setComparer">P i comparer</param>
- public AutoSortList(bool setAutoSort, IComparer<T> setComparer)
- {
- autoSort = setAutoSort;
- comparer = setComparer;
- }
-
- /// <summary>
- /// Create auto sort list
- /// </summary>
- /// <param name="setComparer">Comparer</param>
- public AutoSortList(IComparer<T> setComparer)
- {
- comparer = setComparer;
- }
-
- /// <summary>
- /// Create auto sort list
- /// </summary>
- /// <param name="setAutoSort">Auto sort</param>
- /// <param name="setComparer">Comparer</param>
- /// <param name="setDuplicatesAllowed">Duplicates allowed</param>
- public AutoSortList(
- bool setAutoSort,
- IComparer<T> setComparer,
- bool setDuplicatesAllowed)
- {
- autoSort = setAutoSort;
- comparer = setComparer;
- duplicatesAllowed = setDuplicatesAllowed;
- }
- #endregion
-
- #region ICollection<T> Members
- /// <summary>
- /// Add inserts an item into the collection.
- /// It adds to the end if AutoSort is false. Otherwise, it adds in
- /// the sorted order as determined by BinarySearch(Comparer).
- /// Returns the position inserted into the list.
- /// Will throw the exception InvalidOperationException if called when
- /// AreDuplicatesAllowed is false and you add a duplicate instance.
- /// </summary>
- /// <param name="item">Item to add</param>
- public void Add(T item)
- {
- if (autoSort)
- {
- int index = -1;
- if (data.Count == 0)
- {
- data.Add(item);
- }
- else
- {
- index = BinarySearch(item);
- // not found.
- // vIndex is the bitwise complement of the position to insert
- if (index < 0)
- {
- index = ~index;
- if (index >= Count)
- {
- data.Add(item);
- }
- else
- {
- data.Insert(index, item);
- }
- }
- else if (duplicatesAllowed) // already have one
- {
- data.Insert(index, item);
- }
- else
- {
- throw new InvalidOperationException(
- "The instance " + item + " is a duplicate of one entry " +
- "already in the list.");
- }
- }
- }
- else
- {
- data.Add(item);
- }
- }
-
- /// <summary>
- /// Clear
- /// </summary>
- public void Clear()
- {
- data.Clear();
- }
-
- /// <summary>
- /// Contains is overridden to use BinarySearch when xAutoSort is true.
- /// </summary>
- /// <param name="item">Item to check</param>
- /// <returns>True if the item was found, otherwise false</returns>
- public bool Contains(T item)
- {
- if (autoSort)
- {
- return BinarySearch(item) >= 0;
- }
- else
- {
- return data.Contains(item);
- }
- }
-
- /// <summary>
- /// Copy to
- /// </summary>
- /// <param name="array">Array</param>
- /// <param name="arrayIndex">Array index</param>
- public void CopyTo(T[] array, int arrayIndex)
- {
- data.CopyTo(array, arrayIndex);
- }
-
- /// <summary>
- /// Remove
- /// </summary>
- /// <param name="item">Item</param>
- /// <returns>True if the item was removed, otherwise false</returns>
- public bool Remove(T item)
- {
- return data.Remove(item);
- }
- #endregion
-
- #region IEnumerable Members
- /// <summary>
- /// Get enumerator
- /// </summary>
- /// <returns>IEnumerator</returns>
- IEnumerator IEnumerable.GetEnumerator()
- {
- return data.GetEnumerator();
- }
- #endregion
-
- #region IEnumerable<T> Members
- /// <summary>
- /// Get enumerator
- /// </summary>
- /// <returns>IEnumerator</returns>
- public IEnumerator<T> GetEnumerator()
- {
- return data.GetEnumerator();
- }
- #endregion
-
- #region IList<T> Members
- /// <summary>
- /// IndexOf is overridden to optimize the search using BinarySearch when
- /// AutoSort is true.
- /// </summary>
- /// <param name="item">Item to get index from</param>
- /// <returns>Int</returns>
- public int IndexOf(T item)
- {
- if (AutoSort)
- {
- int index = BinarySearch(item);
- if (index >= 0) // exact match
- {
- return index;
- }
- else
- {
- return -1;
- }
- }
- else
- {
- return data.IndexOf(item);
- }
- }
-
- /// <summary>
- /// Insert is overridden to prevent inserting when AutoSort is true.
- /// You should use Add() instead. Will throw the exception
- /// InvalidOperationException if called when AutoSort is true.
- /// </summary>
- /// <param name="index">Index</param>
- /// <param name="item">Item</param>
- public void Insert(int index, T item)
- {
- if (autoSort)
- {
- throw new InvalidOperationException(
- "Cannot insert into a sorted AutoSortList. " +
- "Use the Add method instead.");
- }
- else
- {
- data.Insert(index, item);
- }
- }
-
- /// <summary>
- /// Remove at
- /// </summary>
- /// <param name="index">Index</param>
- public void RemoveAt(int index)
- {
- data.RemoveAt(index);
- }
- #endregion
-
- #region AddRange (Public)
- /// <summary>
- /// AddRange is overridden to enforce sorting when AutoSort is true.
- /// </summary>
- /// <param name="collection">Collection</param>
- public void AddRange(ICollection<T> collection)
- {
- if (autoSort)
- {
- // Following the documented rules of ArrayList.AddRange:
- // If the new Count (the current Count plus the size of the
- // collection) will be greater than Capacity, the capacity of the
- // list is either doubled or increased to the new Count, whichever
- // is greater.
- if (Count + collection.Count >
- data.Capacity)
- {
- if (data.Capacity * 2 >
- Count + collection.Count)
- {
- data.Capacity = data.Capacity * 2;
- }
- else
- {
- data.Capacity = Count + collection.Count;
- }
- }
-
- foreach (T value in collection)
- {
- Add(value); // this will keep it sorted
- }
- }
- else
- {
- data.AddRange(collection);
- }
- }
- #endregion
-
- #region BinarySearch (Public)
- /// <summary>
- /// BinarySearch is overridden to enforce the comparer property.
- /// </summary>
- /// <param name="item">Item to check</param>
- /// <returns>Item index if the item was found, otherwise -1</returns>
- public int BinarySearch(T item)
- {
- if (autoSort)
- {
- return data.BinarySearch(item, comparer);
- }
- else
- {
- return data.BinarySearch(item);
- }
- }
- #endregion
-
- #region IndexOf (Public)
- /// <summary>
- /// IndexOf is overridden to optimize the search using BinarySearch when
- /// AutoSort is true.
- /// </summary>
- /// <param name="item">Item</param>
- /// <param name="startIndex">Start index</param>
- /// <returns>Int</returns>
- public int IndexOf(T item, int startIndex)
- {
- if (autoSort)
- {
- int index = data.BinarySearch(startIndex,
- Count - startIndex, item, Comparer);
- if (index >= 0) // exact match
- {
- return index;
- }
- else
- {
- return -1;
- }
- }
- else
- {
- return data.IndexOf(item, startIndex);
- }
- }
-
- /// <summary>
- /// IndexOf is overridden to optimize the search using BinarySearch when
- /// AutoSort is true.
- /// </summary>
- /// <param name="item">Item</param>
- /// <param name="startIndex">Start index</param>
- /// <param name="count">Count</param>
- /// <returns>Int</returns>
- public int IndexOf(T item, int startIndex, int count)
- {
- if (autoSort)
- {
- int index = data.BinarySearch(
- startIndex, count, item, comparer);
- if (index >= 0) // exact match
- {
- return index;
- }
- else
- {
- return -1;
- }
- }
- else
- {
- return data.IndexOf(item, startIndex, count);
- }
- }
- #endregion
-
- #region InsertRange (Public)
- /// <summary>
- /// InsertRange is overridden to prevent inserting when AutoSort is true.
- /// You should use AddRange instead. Will throw the exception
- /// InvalidOperationException if called when AutoSort is true.
- /// </summary>
- /// <param name="index">Index</param>
- /// <param name="collection">Collection</param>
- public void InsertRange(int index, ICollection<T> collection)
- {
- if (autoSort)
- {
- throw new InvalidOperationException(
- "Cannot insert into a sorted AutoSortList. " +
- "Use the AddRange method instead.");
- }
- else
- {
- data.InsertRange(index, collection);
- }
- }
- #endregion
-
- #region Sort (Public)
- /// <summary>
- /// Sort is overridden to enforce the Comparer property
- /// </summary>
- public void Sort()
- {
- if (autoSort)
- {
- data.Sort(comparer);
- }
- else
- {
- data.Sort();
- }
- }
- #endregion
-
- #region Reverse (Public)
- /// <summary>
- /// Reverse is overridden to prevent reversing when AutoSort is true.
- /// You should supply an IComparer that reverses the sort and call Sort().
- /// Will throw the exception InvalidOperationException if called when
- /// AutoSort is true.
- /// </summary>
- public void Reverse()
- {
- if (autoSort)
- {
- throw new InvalidOperationException(
- "Cannot reverse into a sorted AutoSortList. " +
- "Use an Comparer whose sort is reversed and call Sort().");
- }
- else
- {
- data.Reverse();
- }
- }
-
- /// <summary>
- /// Reverse is overridden to prevent reversing when AutoSort is true.
- /// You should supply an IComparer that reverses the sort and call Sort().
- /// Will throw the exception InvalidOperationException if called when
- /// AutoSort is true.
- /// </summary>
- /// <param name="index">Index</param>
- /// <param name="count">Count</param>
- public void Reverse(int index, int count)
- {
- if (autoSort)
- {
- throw new InvalidOperationException(
- "Cannot reverse into a sorted AutoSortList. " +
- "Use an Comparer whose sort is reversed and call Sort().");
- }
- else
- {
- data.Reverse(index, count);
- }
- }
- #endregion
- }
-
- /// <summary>
- /// AutoSortList tests helper class, must be outside of the generic
- /// AutoSortList class to be able to test it with TestDriven.net. Also must
- /// be named uniquely for Resharpers run test feature.
- /// </summary>
- public class AutoSortListTests
- {
- #region TestAddingEntries
- /// <summary>
- /// Test adding entries
- /// </summary>
- [Test]
- public void TestAddingEntries()
- {
- AutoSortList<int> testAutoSortList = new AutoSortList<int>();
- testAutoSortList.Add(1);
- testAutoSortList.Add(10);
- testAutoSortList.Add(5);
- testAutoSortList.Add(3);
- // Check if list is sorted
- Assert.Equal("1, 3, 5, 10",
- testAutoSortList.Write());
- }
- #endregion
- }
- }