PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Utilities/Collections/AutoSortList.cs

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