PageRenderTime 43ms CodeModel.GetById 21ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Collections/AutoSortList.cs

#
C# | 668 lines | 389 code | 39 blank | 240 comment | 23 complexity | 8144e08acce65d5d90d9d83938017d93 MD5 | raw file
  1using System;
  2using System.Collections;
  3using System.Collections.Generic;
  4using Delta.Utilities.Helpers;
  5using NUnit.Framework;
  6
  7namespace Delta.Utilities.Collections
  8{
  9	/// <summary>
 10	/// AutoSortArrayList extends the ArrayList to manage a list that is
 11	/// always sorted.
 12	/// </summary>
 13	/// <remarks>
 14	/// Details: AutoSortArrayList is basically an ArrayList which sorts
 15	/// itself whenever we add or remove items. The list does not use a key to
 16	/// identify each instance.
 17	/// The .NET framework offers similar classes, each with shortcomings.
 18	/// * ArrayList - Can sort any type of object but cannot automatically add
 19	///		instances into the correct sorted position.
 20	/// * SortedList - Can keep any type of object in a sorted order. However,
 21	///		it demands a key for every instance. If you don't manage your objects
 22	///		by name, this won't work.
 23	/// * StringsCollection - Can keep a list of strings without the requirement
 24	///		of a key. However, it can't keep them sorted.
 25	/// AutoSortArrayList is based on ArrayList and overrides any method that
 26	/// dictates order, such as Add(), Insert(), and AddRange(). It overrides
 27	/// methods that lookup data so that a binary search is used for speed.
 28	/// It overrides methods that allow the user to control the location of
 29	/// inserted data and throws InvalidOperationExceptions.
 30	///
 31	/// AutoSortArrayList can be used with any class that you want to keep
 32	/// ordered. It determines order with an object that implements the
 33	/// IComparer interface. If you are collecting primative types like int
 34	/// and double, the default IComparer methodology is used. If you are
 35	/// collecting strings and need case insensitivity, .NET provides
 36	/// System.Collections.CaseInsensitiveComparer.
 37	///
 38	/// Here is an overview of the key properties of AutoSortArrayList:
 39	/// * AutoSort - when true, Add() will insert in a sorted order.
 40	///		Defaults to true.
 41	/// * Comparer - use this to supply a custom IComparer. Defaults to null.
 42	/// * AreDuplicatesAllowed - when true, Add() will permit duplicate instances
 43	///		(as determined by BinarySearch() finding an exact match in the list.
 44	///		Defaults to false.
 45	/// There are several constructors, designed to override these property's
 46	/// defaults.
 47	/// </remarks>
 48	/// <typeparam name="T">Data type</typeparam>
 49	public class AutoSortList<T> : IList<T>, ICollection<T>, IEnumerable<T>
 50	{
 51		#region AutoSort (Public)
 52		/// <summary>
 53		/// AutoSort indicates if auto sorting is on.
 54		/// When true, Add() and AddRange() will insert the object in the
 55		/// position provided by BinarySearch. A number of methods will raise
 56		/// InvalidOperationException when true: Insert(), InsertRange(), and
 57		/// Reverse(). When false, all methods follow use their ancestor'str
 58		/// functionality. If set to false and you add items, when you set it
 59		/// back to true, Add() and AddRange() can incorrectly position data
 60		/// as a binary search is used to position data. So if switching from
 61		/// false to true, call Sort().
 62		/// </summary>
 63		/// <typeparam name="T">T</typeparam>
 64		public bool AutoSort
 65		{
 66			get
 67			{
 68				return autoSort;
 69			}
 70		}
 71		#endregion
 72
 73		#region Comparer (Public)
 74		/// <summary>
 75		/// Comparer provides an IComparer interface for use with Sort() and
 76		/// BinarySearch(). By default, these methods use the IComparer methods
 77		/// of the objects you insert into the list. For primitive values, this
 78		/// works well. For strings, you may consider System.Collections.
 79		/// CaseInsensitiveComparer. But for custom objects, you'll need to help
 80		/// with an IComparer.
 81		/// Defaults to null.
 82		/// </summary>
 83		/// <typeparam name="T">T</typeparam>
 84		public IComparer<T> Comparer
 85		{
 86			get
 87			{
 88				return comparer;
 89			}
 90			set
 91			{
 92				comparer = value;
 93				Sort();
 94			}
 95		}
 96		#endregion
 97
 98		#region AreDuplicatesAllowed (Public)
 99		/// <summary>
100		/// AreDuplicatesAllowed determines if a duplicate is allowed into the list.
101		/// This only applies with AutoSort is true.
102		/// If Add() detects a duplicate when this is false, an
103		/// InvalidOperationException is thrown.
104		/// Defaults to false.
105		/// </summary>
106		/// <typeparam name="T">T</typeparam>
107		public bool AreDuplicatesAllowed
108		{
109			get
110			{
111				return duplicatesAllowed;
112			}
113		}
114		#endregion
115
116		#region Item (Public)
117		/// <summary>
118		/// This
119		/// </summary>
120		/// <param name="index">Index</param>
121		/// <returns>T</returns>
122		public T this[int index]
123		{
124			get
125			{
126				return data[index];
127			}
128			set
129			{
130				data[index] = value;
131			}
132		}
133		#endregion
134
135		#region Count (Public)
136		/// <summary>
137		/// Count
138		/// </summary>
139		/// <returns>Int</returns>
140		public int Count
141		{
142			get
143			{
144				return data.Count;
145			}
146		}
147		#endregion
148
149		#region IsReadOnly (Public)
150		/// <summary>
151		/// Is read only
152		/// </summary>
153		/// <returns>
154		/// True if the list is readonly. Will always return false, this feature is
155		/// not supported by AutoSortList).
156		/// </returns>
157		public bool IsReadOnly
158		{
159			get
160			{
161				return false;
162			}
163		}
164		#endregion
165
166		#region Protected
167
168		#region autoSort (Protected)
169		/// <summary>
170		/// Auto sorting on?
171		/// </summary>
172		/// <typeparam name="T">T</typeparam>
173		protected bool autoSort = true;
174		#endregion
175
176		#region comparer (Protected)
177		/// <summary>
178		/// Comparer for sort.
179		/// </summary>
180		/// <typeparam name="T">T</typeparam>
181		protected IComparer<T> comparer;
182		#endregion
183
184		#region duplicatesAllowed (Protected)
185		/// <summary>
186		/// Duplicates allowed?
187		/// </summary>
188		/// <typeparam name="T">T</typeparam>
189		protected bool duplicatesAllowed;
190		#endregion
191
192		#endregion
193
194		#region Private
195
196		#region data (Private)
197		/// <summary>
198		/// Using List&lt;T&gt;, we can't derive from it to override the methods.
199		/// </summary>
200		/// <typeparam name="T">T</typeparam>
201		private readonly List<T> data = new List<T>();
202		#endregion
203
204		#endregion
205
206		#region Constructors
207		/// <summary>
208		/// Create auto sort list
209		/// </summary>
210		public AutoSortList()
211		{
212		}
213
214		/// <summary>
215		/// Create auto sort list
216		/// </summary>
217		/// <param name="setAutoSort">Auto sort</param>
218		public AutoSortList(bool setAutoSort)
219		{
220			autoSort = setAutoSort;
221		}
222
223		/// <summary>
224		/// Create auto sort list
225		/// </summary>
226		/// <param name="setAutoSort">Auto sort</param>
227		/// <param name="setComparer">P i comparer</param>
228		public AutoSortList(bool setAutoSort, IComparer<T> setComparer)
229		{
230			autoSort = setAutoSort;
231			comparer = setComparer;
232		}
233
234		/// <summary>
235		/// Create auto sort list
236		/// </summary>
237		/// <param name="setComparer">Comparer</param>
238		public AutoSortList(IComparer<T> setComparer)
239		{
240			comparer = setComparer;
241		}
242
243		/// <summary>
244		/// Create auto sort list
245		/// </summary>
246		/// <param name="setAutoSort">Auto sort</param>
247		/// <param name="setComparer">Comparer</param>
248		/// <param name="setDuplicatesAllowed">Duplicates allowed</param>
249		public AutoSortList(
250			bool setAutoSort,
251			IComparer<T> setComparer,
252			bool setDuplicatesAllowed)
253		{
254			autoSort = setAutoSort;
255			comparer = setComparer;
256			duplicatesAllowed = setDuplicatesAllowed;
257		}
258		#endregion
259
260		#region ICollection<T> Members
261		/// <summary>
262		/// Add inserts an item into the collection.
263		/// It adds to the end if AutoSort is false. Otherwise, it adds in
264		/// the sorted order as determined by BinarySearch(Comparer).
265		/// Returns the position inserted into the list.
266		/// Will throw the exception InvalidOperationException if called when
267		/// AreDuplicatesAllowed is false and you add a duplicate instance.
268		/// </summary>
269		/// <param name="item">Item to add</param>
270		public void Add(T item)
271		{
272			if (autoSort)
273			{
274				int index = -1;
275				if (data.Count == 0)
276				{
277					data.Add(item);
278				}
279				else
280				{
281					index = BinarySearch(item);
282					// not found.
283					// vIndex is the bitwise complement of the position to insert
284					if (index < 0)
285					{
286						index = ~index;
287						if (index >= Count)
288						{
289							data.Add(item);
290						}
291						else
292						{
293							data.Insert(index, item);
294						}
295					}
296					else if (duplicatesAllowed) // already have one
297					{
298						data.Insert(index, item);
299					}
300					else
301					{
302						throw new InvalidOperationException(
303							"The instance " + item + " is a duplicate of one entry " +
304							"already in the list.");
305					}
306				}
307			}
308			else
309			{
310				data.Add(item);
311			}
312		}
313
314		/// <summary>
315		/// Clear
316		/// </summary>
317		public void Clear()
318		{
319			data.Clear();
320		}
321
322		/// <summary>
323		/// Contains is overridden to use BinarySearch when xAutoSort is true.
324		/// </summary>
325		/// <param name="item">Item to check</param>
326		/// <returns>True if the item was found, otherwise false</returns>
327		public bool Contains(T item)
328		{
329			if (autoSort)
330			{
331				return BinarySearch(item) >= 0;
332			}
333			else
334			{
335				return data.Contains(item);
336			}
337		}
338
339		/// <summary>
340		/// Copy to
341		/// </summary>
342		/// <param name="array">Array</param>
343		/// <param name="arrayIndex">Array index</param>
344		public void CopyTo(T[] array, int arrayIndex)
345		{
346			data.CopyTo(array, arrayIndex);
347		}
348
349		/// <summary>
350		/// Remove
351		/// </summary>
352		/// <param name="item">Item</param>
353		/// <returns>True if the item was removed, otherwise false</returns>
354		public bool Remove(T item)
355		{
356			return data.Remove(item);
357		}
358		#endregion
359
360		#region IEnumerable Members
361		/// <summary>
362		/// Get enumerator
363		/// </summary>
364		/// <returns>IEnumerator</returns>
365		IEnumerator IEnumerable.GetEnumerator()
366		{
367			return data.GetEnumerator();
368		}
369		#endregion
370
371		#region IEnumerable<T> Members
372		/// <summary>
373		/// Get enumerator
374		/// </summary>
375		/// <returns>IEnumerator</returns>
376		public IEnumerator<T> GetEnumerator()
377		{
378			return data.GetEnumerator();
379		}
380		#endregion
381
382		#region IList<T> Members
383		/// <summary>
384		/// IndexOf is overridden to optimize the search using BinarySearch when
385		/// AutoSort is true.
386		/// </summary>
387		/// <param name="item">Item to get index from</param>
388		/// <returns>Int</returns>
389		public int IndexOf(T item)
390		{
391			if (AutoSort)
392			{
393				int index = BinarySearch(item);
394				if (index >= 0) // exact match
395				{
396					return index;
397				}
398				else
399				{
400					return -1;
401				}
402			}
403			else
404			{
405				return data.IndexOf(item);
406			}
407		}
408
409		/// <summary>
410		/// Insert is overridden to prevent inserting when AutoSort is true.
411		/// You should use Add() instead. Will throw the exception
412		/// InvalidOperationException if called when AutoSort is true.
413		/// </summary>
414		/// <param name="index">Index</param>
415		/// <param name="item">Item</param>
416		public void Insert(int index, T item)
417		{
418			if (autoSort)
419			{
420				throw new InvalidOperationException(
421					"Cannot insert into a sorted AutoSortList. " +
422					"Use the Add method instead.");
423			}
424			else
425			{
426				data.Insert(index, item);
427			}
428		}
429
430		/// <summary>
431		/// Remove at
432		/// </summary>
433		/// <param name="index">Index</param>
434		public void RemoveAt(int index)
435		{
436			data.RemoveAt(index);
437		}
438		#endregion
439
440		#region AddRange (Public)
441		/// <summary>
442		/// AddRange is overridden to enforce sorting when AutoSort is true.
443		/// </summary>
444		/// <param name="collection">Collection</param>
445		public void AddRange(ICollection<T> collection)
446		{
447			if (autoSort)
448			{
449				// Following the documented rules of ArrayList.AddRange:
450				// If the new Count (the current Count plus the size of the
451				// collection) will be greater than Capacity, the capacity of the
452				// list is either doubled or increased to the new Count, whichever
453				// is greater.
454				if (Count + collection.Count >
455				    data.Capacity)
456				{
457					if (data.Capacity * 2 >
458					    Count + collection.Count)
459					{
460						data.Capacity = data.Capacity * 2;
461					}
462					else
463					{
464						data.Capacity = Count + collection.Count;
465					}
466				}
467
468				foreach (T value in collection)
469				{
470					Add(value); // this will keep it sorted
471				}
472			}
473			else
474			{
475				data.AddRange(collection);
476			}
477		}
478		#endregion
479
480		#region BinarySearch (Public)
481		/// <summary>
482		/// BinarySearch is overridden to enforce the comparer property.
483		/// </summary>
484		/// <param name="item">Item to check</param>
485		/// <returns>Item index if the item was found, otherwise -1</returns>
486		public int BinarySearch(T item)
487		{
488			if (autoSort)
489			{
490				return data.BinarySearch(item, comparer);
491			}
492			else
493			{
494				return data.BinarySearch(item);
495			}
496		}
497		#endregion
498
499		#region IndexOf (Public)
500		/// <summary>
501		/// IndexOf is overridden to optimize the search using BinarySearch when
502		/// AutoSort is true.
503		/// </summary>
504		/// <param name="item">Item</param>
505		/// <param name="startIndex">Start index</param>
506		/// <returns>Int</returns>
507		public int IndexOf(T item, int startIndex)
508		{
509			if (autoSort)
510			{
511				int index = data.BinarySearch(startIndex,
512					Count - startIndex, item, Comparer);
513				if (index >= 0) // exact match
514				{
515					return index;
516				}
517				else
518				{
519					return -1;
520				}
521			}
522			else
523			{
524				return data.IndexOf(item, startIndex);
525			}
526		}
527
528		/// <summary>
529		/// IndexOf is overridden to optimize the search using BinarySearch when
530		/// AutoSort is true.
531		/// </summary>
532		/// <param name="item">Item</param>
533		/// <param name="startIndex">Start index</param>
534		/// <param name="count">Count</param>
535		/// <returns>Int</returns>
536		public int IndexOf(T item, int startIndex, int count)
537		{
538			if (autoSort)
539			{
540				int index = data.BinarySearch(
541					startIndex, count, item, comparer);
542				if (index >= 0) // exact match
543				{
544					return index;
545				}
546				else
547				{
548					return -1;
549				}
550			}
551			else
552			{
553				return data.IndexOf(item, startIndex, count);
554			}
555		}
556		#endregion
557
558		#region InsertRange (Public)
559		/// <summary>
560		/// InsertRange is overridden to prevent inserting when AutoSort is true.
561		/// You should use AddRange instead. Will throw the exception
562		/// InvalidOperationException if called when AutoSort is true.
563		/// </summary>
564		/// <param name="index">Index</param>
565		/// <param name="collection">Collection</param>
566		public void InsertRange(int index, ICollection<T> collection)
567		{
568			if (autoSort)
569			{
570				throw new InvalidOperationException(
571					"Cannot insert into a sorted AutoSortList. " +
572					"Use the AddRange method instead.");
573			}
574			else
575			{
576				data.InsertRange(index, collection);
577			}
578		}
579		#endregion
580
581		#region Sort (Public)
582		/// <summary>
583		/// Sort is overridden to enforce the Comparer property
584		/// </summary>
585		public void Sort()
586		{
587			if (autoSort)
588			{
589				data.Sort(comparer);
590			}
591			else
592			{
593				data.Sort();
594			}
595		}
596		#endregion
597
598		#region Reverse (Public)
599		/// <summary>
600		/// Reverse is overridden to prevent reversing when AutoSort is true.
601		/// You should supply an IComparer that reverses the sort and call Sort().
602		/// Will throw the exception InvalidOperationException if called when
603		/// AutoSort is true.
604		/// </summary>
605		public void Reverse()
606		{
607			if (autoSort)
608			{
609				throw new InvalidOperationException(
610					"Cannot reverse into a sorted AutoSortList. " +
611					"Use an Comparer whose sort is reversed and call Sort().");
612			}
613			else
614			{
615				data.Reverse();
616			}
617		}
618
619		/// <summary>
620		/// Reverse is overridden to prevent reversing when AutoSort is true.
621		/// You should supply an IComparer that reverses the sort and call Sort().
622		/// Will throw the exception InvalidOperationException if called when
623		/// AutoSort is true.
624		/// </summary>
625		/// <param name="index">Index</param>
626		/// <param name="count">Count</param>
627		public void Reverse(int index, int count)
628		{
629			if (autoSort)
630			{
631				throw new InvalidOperationException(
632					"Cannot reverse into a sorted AutoSortList. " +
633					"Use an Comparer whose sort is reversed and call Sort().");
634			}
635			else
636			{
637				data.Reverse(index, count);
638			}
639		}
640		#endregion
641	}
642
643	/// <summary>
644	/// AutoSortList tests helper class, must be outside of the generic
645	/// AutoSortList class to be able to test it with TestDriven.net. Also must
646	/// be named uniquely for Resharpers run test feature.
647	/// </summary>
648	public class AutoSortListTests
649	{
650		#region TestAddingEntries
651		/// <summary>
652		/// Test adding entries
653		/// </summary>
654		[Test]
655		public void TestAddingEntries()
656		{
657			AutoSortList<int> testAutoSortList = new AutoSortList<int>();
658			testAutoSortList.Add(1);
659			testAutoSortList.Add(10);
660			testAutoSortList.Add(5);
661			testAutoSortList.Add(3);
662			// Check if list is sorted
663			Assert.Equal("1, 3, 5, 10",
664				testAutoSortList.Write());
665		}
666		#endregion
667	}
668}