PageRenderTime 70ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/Source/PowerCollections/BigList.cs

#
C# | 2792 lines | 1424 code | 290 blank | 1078 comment | 473 complexity | 9d613785f8b56466664c01e883b7f485 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception

Large files files are truncated, but you can click here to view the full file

  1. //******************************
  2. // Written by Peter Golde
  3. // Copyright (c) 2004-2007, Wintellect
  4. //
  5. // Use and restribution of this code is subject to the license agreement
  6. // contained in the file "License.txt" accompanying this file.
  7. //******************************
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. // CONSIDER: provide more efficient implementation of CopyTo.
  12. namespace Wintellect.PowerCollections
  13. {
  14. /// <summary>
  15. /// BigList&lt;T&gt; provides a list of items, in order, with indices of the items ranging from 0 to one less
  16. /// than the count of items in the collection. BigList&lt;T&gt; is optimized for efficient operations on large (&gt;100 items)
  17. /// lists, especially for insertions, deletions, copies, and concatinations.
  18. /// </summary>
  19. /// <remarks>
  20. /// <para>BigList&lt;T&gt; class is similar in functionality to the standard List&lt;T&gt; class. Both classes
  21. /// provide a collection that stores an set of items in order, with indices of the items ranging from 0 to one less
  22. /// than the count of items in the collection. Both classes provide the ability to add and remove items from any index,
  23. /// and the get or set the item at any index.</para>
  24. /// <para>BigList&lt;T&gt; differs significantly from List&lt;T&gt; in the performance of various operations,
  25. /// especially when the lists become large (several hundred items or more). With List&lt;T&gt;, inserting or removing
  26. /// elements from anywhere in a large list except the end is very inefficient -- every item after the point of inserting
  27. /// or deletion has to be moved in the list. The BigList&lt;T&gt; class, however, allows for fast insertions
  28. /// and deletions anywhere in the list. Furthermore, BigList&lt;T&gt; allows copies of a list, sub-parts
  29. /// of a list, and concatinations of two lists to be very fast. When a copy is made of part or all of a BigList,
  30. /// two lists shared storage for the parts of the lists that are the same. Only when one of the lists is changed is additional
  31. /// memory allocated to store the distinct parts of the lists.</para>
  32. /// <para>Of course, there is a small price to pay for this extra flexibility. Although still quite efficient, using an
  33. /// index to get or change one element of a BigList, while still reasonably efficient, is significantly slower than using
  34. /// a plain List. Because of this, if you want to process every element of a BigList, using a foreach loop is a lot
  35. /// more efficient than using a for loop and indexing the list.</para>
  36. /// <para>In general, use a List when the only operations you are using are Add (to the end), foreach,
  37. /// or indexing, or you are very sure the list will always remain small (less than 100 items). For large (&gt;100 items) lists
  38. /// that do insertions, removals, copies, concatinations, or sub-ranges, BigList will be more efficient than List.
  39. /// In almost all cases, BigList is more efficient and easier to use than LinkedList.</para>
  40. /// </remarks>
  41. /// <typeparam name="T">The type of items to store in the BigList.</typeparam>
  42. [Serializable]
  43. public class BigList<T>: ListBase<T>, ICloneable
  44. {
  45. const uint MAXITEMS = int.MaxValue - 1; // maximum number of items in a BigList.
  46. #if DEBUG
  47. const int MAXLEAF = 8; // Maximum number of elements in a leaf node -- small for debugging purposes.
  48. #else
  49. const int MAXLEAF = 120; // Maximum number of elements in a leaf node.
  50. #endif
  51. const int BALANCEFACTOR = 6; // how far the root must be in depth from fully balanced to invoke the rebalance operation (min 3).
  52. // The fibonacci numbers. Used in the rebalancing algorithm. Final MaxValue makes sure we don't go off the end.
  53. static readonly int[] FIBONACCI = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,
  54. 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040,
  55. 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986,
  56. 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, int.MaxValue};
  57. const int MAXFIB = 44; // maximum index in the above, not counting the final MaxValue.
  58. // If null, the BigList is empty. If non-null, the list has at least one item.
  59. private Node root;
  60. // Holds the change stamp for the collection.
  61. private int changeStamp;
  62. /// <summary>
  63. /// Must be called whenever there is a structural change in the tree. Causes
  64. /// changeStamp to be changed, which causes any in-progress enumerations
  65. /// to throw exceptions.
  66. /// </summary>
  67. private void StopEnumerations()
  68. {
  69. ++changeStamp;
  70. }
  71. /// <summary>
  72. /// Checks the given stamp against the current change stamp. If different, the
  73. /// collection has changed during enumeration and an InvalidOperationException
  74. /// must be thrown
  75. /// </summary>
  76. /// <param name="startStamp">changeStamp at the start of the enumeration.</param>
  77. private void CheckEnumerationStamp(int startStamp)
  78. {
  79. if (startStamp != changeStamp) {
  80. throw new InvalidOperationException(Strings.ChangeDuringEnumeration);
  81. }
  82. }
  83. /// <summary>
  84. /// Creates a new BigList. The BigList is initially empty.
  85. /// </summary>
  86. /// <remarks>Creating a empty BigList takes constant time and consumes a very small amount of memory.</remarks>
  87. public BigList()
  88. {
  89. root = null;
  90. }
  91. /// <summary>
  92. /// Creates a new BigList initialized with the items from <paramref name="collection"/>, in order.
  93. /// </summary>
  94. /// <remarks>Initializing the tree list with the elements of collection takes time O(N), where N is the number of
  95. /// items in <paramref name="collection"/>.</remarks>
  96. /// <param name="collection">The collection used to initialize the BigList. </param>
  97. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  98. public BigList(IEnumerable<T> collection)
  99. {
  100. if (collection == null)
  101. throw new ArgumentNullException("collection");
  102. root = NodeFromEnumerable(collection);
  103. CheckBalance();
  104. }
  105. /// <summary>
  106. /// Creates a new BigList initialized with a given number of copies of the items from <paramref name="collection"/>, in order.
  107. /// </summary>
  108. /// <remarks>Initializing the tree list with the elements of collection takes time O(N + log K), where N is the number of
  109. /// items in <paramref name="collection"/>, and K is the number of copies.</remarks>
  110. /// <param name="copies">Number of copies of the collection to use.</param>
  111. /// <param name="collection">The collection used to initialize the BigList. </param>
  112. /// <exception cref="ArgumentOutOfRangeException"><paramref name="copies"/> is negative.</exception>
  113. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  114. public BigList(IEnumerable<T> collection, int copies)
  115. {
  116. if (collection == null)
  117. throw new ArgumentNullException("collection");
  118. root = NCopiesOfNode(copies, NodeFromEnumerable(collection));
  119. CheckBalance();
  120. }
  121. /// <summary>
  122. /// Creates a new BigList that is a copy of <paramref name="list"/>.
  123. /// </summary>
  124. /// <remarks>Copying a BigList takes constant time, and little
  125. /// additional memory, since the storage for the items of the two lists is shared. However, changing
  126. /// either list will take additional time and memory. Portions of the list are copied when they are changed.</remarks>
  127. /// <param name="list">The BigList to copy. </param>
  128. /// <exception cref="ArgumentNullException"><paramref name="list"/> is null.</exception>
  129. public BigList(BigList<T> list)
  130. {
  131. if (list == null)
  132. throw new ArgumentNullException("list");
  133. if (list.root == null)
  134. root = null;
  135. else {
  136. list.root.MarkShared();
  137. root = list.root;
  138. }
  139. }
  140. /// <summary>
  141. /// Creates a new BigList that is several copies of <paramref name="list"/>.
  142. /// </summary>
  143. /// <remarks>Creating K copies of a BigList takes time O(log K), and O(log K)
  144. /// additional memory, since the storage for the items of the two lists is shared. However, changing
  145. /// either list will take additional time and memory. Portions of the list are copied when they are changed.</remarks>
  146. /// <param name="copies">Number of copies of the collection to use.</param>
  147. /// <param name="list">The BigList to copy. </param>
  148. /// <exception cref="ArgumentNullException"><paramref name="list"/> is null.</exception>
  149. public BigList(BigList<T> list, int copies)
  150. {
  151. if (list == null)
  152. throw new ArgumentNullException("list");
  153. if (list.root == null)
  154. root = null;
  155. else {
  156. list.root.MarkShared();
  157. root = NCopiesOfNode(copies, list.root);
  158. }
  159. }
  160. /// <summary>
  161. /// Creates a new BigList from the indicated Node.
  162. /// </summary>
  163. /// <param name="node">Node that becomes the new root. If null, the new BigList is empty.</param>
  164. private BigList(Node node)
  165. {
  166. this.root = node;
  167. CheckBalance();
  168. }
  169. /// <summary>
  170. /// Gets the number of items stored in the BigList. The indices of the items
  171. /// range from 0 to Count-1.
  172. /// </summary>
  173. /// <remarks>Getting the number of items in the BigList takes constant time.</remarks>
  174. /// <value>The number of items in the BigList.</value>
  175. public sealed override int Count
  176. {
  177. get
  178. {
  179. if (root == null)
  180. return 0;
  181. else
  182. return root.Count;
  183. }
  184. }
  185. /// <summary>
  186. /// Gets or sets an item in the list, by index.
  187. /// </summary>
  188. /// <remarks><para> Gettingor setting an item takes time O(log N), where N is the number of items
  189. /// in the list.</para>
  190. /// <para>To process each of the items in the list, using GetEnumerator() or a foreach loop is more efficient
  191. /// that accessing each of the elements by index.</para></remarks>
  192. /// <param name="index">The index of the item to get or set. The first item in the list
  193. /// has index 0, the last item has index Count-1.</param>
  194. /// <returns>The value of the item at the given index.</returns>
  195. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or
  196. /// greater than or equal to Count.</exception>
  197. public sealed override T this[int index]
  198. {
  199. get
  200. {
  201. // This could just be a simple call to GetAt on the root.
  202. // It is recoded as an interative algorithm for performance.
  203. if (root == null || index < 0 || index >= root.Count)
  204. throw new ArgumentOutOfRangeException("index");
  205. Node current = root;
  206. ConcatNode curConcat = current as ConcatNode;
  207. while (curConcat != null) {
  208. int leftCount = curConcat.left.Count;
  209. if (index < leftCount)
  210. current = curConcat.left;
  211. else {
  212. current = curConcat.right;
  213. index -= leftCount;
  214. }
  215. curConcat = current as ConcatNode;
  216. }
  217. LeafNode curLeaf = (LeafNode)current;
  218. return curLeaf.items[index];
  219. }
  220. set
  221. {
  222. // This could just be a simple call to SetAtInPlace on the root.
  223. // It is recoded as an interative algorithm for performance.
  224. if (root == null || index < 0 || index >= root.Count)
  225. throw new ArgumentOutOfRangeException("index");
  226. // Like List<T>, we stop enumerations after a set operation. This could be made
  227. // to not happen, but it would be complex, because set operations on a shared node
  228. // could change the node.
  229. StopEnumerations();
  230. if (root.Shared)
  231. root = root.SetAt(index, value);
  232. Node current = root;
  233. ConcatNode curConcat = current as ConcatNode;
  234. while (curConcat != null) {
  235. int leftCount = curConcat.left.Count;
  236. if (index < leftCount) {
  237. current = curConcat.left;
  238. if (current.Shared) {
  239. curConcat.left = current.SetAt(index, value);
  240. return;
  241. }
  242. }
  243. else {
  244. current = curConcat.right;
  245. index -= leftCount;
  246. if (current.Shared) {
  247. curConcat.right = current.SetAt(index, value);
  248. return;
  249. }
  250. }
  251. curConcat = current as ConcatNode;
  252. }
  253. LeafNode curLeaf = (LeafNode)current;
  254. curLeaf.items[index] = value;
  255. }
  256. }
  257. /// <summary>
  258. /// Removes all of the items from the BigList.
  259. /// </summary>
  260. /// <remarks>Clearing a BigList takes constant time.</remarks>
  261. public sealed override void Clear()
  262. {
  263. StopEnumerations();
  264. root = null;
  265. }
  266. /// <summary>
  267. /// Inserts a new item at the given index in the BigList. All items at indexes
  268. /// equal to or greater than <paramref name="index"/> move up one index.
  269. /// </summary>
  270. /// <remarks>The amount of time to insert an item is O(log N), no matter where
  271. /// in the list the insertion occurs. Inserting an item at the beginning or end of the
  272. /// list is O(N).
  273. /// </remarks>
  274. /// <param name="index">The index to insert the item at. After the
  275. /// insertion, the inserted item is located at this index. The
  276. /// first item has index 0.</param>
  277. /// <param name="item">The item to insert at the given index.</param>
  278. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is
  279. /// less than zero or greater than Count.</exception>
  280. public sealed override void Insert(int index, T item)
  281. {
  282. StopEnumerations();
  283. if ((uint)Count + 1 > MAXITEMS)
  284. throw new InvalidOperationException(Strings.CollectionTooLarge);
  285. if (index <= 0 || index >= Count) {
  286. if (index == 0)
  287. AddToFront(item);
  288. else if (index == Count)
  289. Add(item);
  290. else
  291. throw new ArgumentOutOfRangeException("index");
  292. }
  293. else {
  294. if (root == null)
  295. root = new LeafNode(item);
  296. else {
  297. Node newRoot = root.InsertInPlace(index, item);
  298. if (newRoot != root) {
  299. root = newRoot;
  300. CheckBalance();
  301. }
  302. }
  303. }
  304. }
  305. /// <summary>
  306. /// Inserts a collection of items at the given index in the BigList. All items at indexes
  307. /// equal to or greater than <paramref name="index"/> increase their indices
  308. /// by the number of items inserted.
  309. /// </summary>
  310. /// <remarks>The amount of time to insert an arbitrary collection in the BigList is O(M + log N),
  311. /// where M is the number of items inserted, and N is the number of items in the list.
  312. /// </remarks>
  313. /// <param name="index">The index to insert the collection at. After the
  314. /// insertion, the first item of the inserted collection is located at this index. The
  315. /// first item has index 0.</param>
  316. /// <param name="collection">The collection of items to insert at the given index.</param>
  317. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is
  318. /// less than zero or greater than Count.</exception>
  319. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  320. public void InsertRange(int index, IEnumerable<T> collection)
  321. {
  322. StopEnumerations();
  323. if (collection == null)
  324. throw new ArgumentNullException("collection");
  325. if (index <= 0 || index >= Count) {
  326. if (index == 0)
  327. AddRangeToFront(collection);
  328. else if (index == Count)
  329. AddRange(collection);
  330. else
  331. throw new ArgumentOutOfRangeException("index");
  332. }
  333. else {
  334. Node node = NodeFromEnumerable(collection);
  335. if (node == null)
  336. return;
  337. else if (root == null)
  338. root = node;
  339. else {
  340. if ((uint)Count + (uint)node.Count > MAXITEMS)
  341. throw new InvalidOperationException(Strings.CollectionTooLarge);
  342. Node newRoot = root.InsertInPlace(index, node, true);
  343. if (newRoot != root) {
  344. root = newRoot;
  345. CheckBalance();
  346. }
  347. }
  348. }
  349. }
  350. /// <summary>
  351. /// Inserts a BigList of items at the given index in the BigList. All items at indexes
  352. /// equal to or greater than <paramref name="index"/> increase their indices
  353. /// by the number of items inserted.
  354. /// </summary>
  355. /// <remarks>The amount of time to insert another BigList is O(log N),
  356. /// where N is the number of items in the list, regardless of the number of items in the
  357. /// inserted list. Storage is shared between the two lists until one of them is changed.
  358. /// </remarks>
  359. /// <param name="index">The index to insert the collection at. After the
  360. /// insertion, the first item of the inserted collection is located at this index. The
  361. /// first item has index 0.</param>
  362. /// <param name="list">The BigList of items to insert at the given index.</param>
  363. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is
  364. /// less than zero or greater than Count.</exception>
  365. /// <exception cref="ArgumentNullException"><paramref name="list"/> is null.</exception>
  366. public void InsertRange(int index, BigList<T> list)
  367. {
  368. StopEnumerations();
  369. if (list == null)
  370. throw new ArgumentNullException("list");
  371. if ((uint)Count + (uint)list.Count > MAXITEMS)
  372. throw new InvalidOperationException(Strings.CollectionTooLarge);
  373. if (index <= 0 || index >= Count) {
  374. if (index == 0)
  375. AddRangeToFront(list);
  376. else if (index == Count)
  377. AddRange(list);
  378. else
  379. throw new ArgumentOutOfRangeException("index");
  380. }
  381. else {
  382. if (list.Count == 0)
  383. return;
  384. if (root == null) {
  385. list.root.MarkShared();
  386. root = list.root;
  387. }
  388. else {
  389. if (list.root == root)
  390. root.MarkShared(); // make sure inserting into itself works.
  391. Node newRoot = root.InsertInPlace(index, list.root, false);
  392. if (newRoot != root) {
  393. root = newRoot;
  394. CheckBalance();
  395. }
  396. }
  397. }
  398. }
  399. /// <summary>
  400. /// Removes the item at the given index in the BigList. All items at indexes
  401. /// greater than <paramref name="index"/> move down one index.
  402. /// </summary>
  403. /// <remarks>The amount of time to delete an item in the BigList is O(log N),
  404. /// where N is the number of items in the list.
  405. /// </remarks>
  406. /// <param name="index">The index in the list to remove the item at. The
  407. /// first item in the list has index 0.</param>
  408. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is
  409. /// less than zero or greater than or equal to Count.</exception>
  410. public sealed override void RemoveAt(int index)
  411. {
  412. RemoveRange(index, 1);
  413. }
  414. /// <summary>
  415. /// Removes a range of items at the given index in the Deque. All items at indexes
  416. /// greater than <paramref name="index"/> move down <paramref name="count"/> indices
  417. /// in the Deque.
  418. /// </summary>
  419. /// <remarks>The amount of time to delete <paramref name="count"/> items in the Deque is proportional
  420. /// to the distance of index from the closest end of the Deque, plus <paramref name="count"/>:
  421. /// O(count + Min(<paramref name="index"/>, Count - 1 - <paramref name="index"/>)).
  422. /// </remarks>
  423. /// <param name="index">The index in the list to remove the range at. The
  424. /// first item in the list has index 0.</param>
  425. /// <param name="count">The number of items to remove.</param>
  426. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is
  427. /// less than zero or greater than or equal to Count, or <paramref name="count"/> is less than zero
  428. /// or too large.</exception>
  429. public void RemoveRange(int index, int count)
  430. {
  431. if (count == 0)
  432. return; // nothing to do.
  433. if (index < 0 || index >= Count)
  434. throw new ArgumentOutOfRangeException("index");
  435. if (count < 0 || count > Count - index)
  436. throw new ArgumentOutOfRangeException("count");
  437. StopEnumerations();
  438. Node newRoot = root.RemoveRangeInPlace(index, index + count - 1);
  439. if (newRoot != root) {
  440. root = newRoot;
  441. CheckBalance();
  442. }
  443. }
  444. /// <summary>
  445. /// Adds an item to the end of the BigList. The indices of all existing items
  446. /// in the Deque are unchanged.
  447. /// </summary>
  448. /// <remarks>Adding an item takes, on average, constant time.</remarks>
  449. /// <param name="item">The item to add.</param>
  450. public sealed override void Add(T item)
  451. {
  452. if ((uint)Count + 1 > MAXITEMS)
  453. throw new InvalidOperationException(Strings.CollectionTooLarge);
  454. StopEnumerations();
  455. if (root == null)
  456. root = new LeafNode(item);
  457. else {
  458. Node newRoot = root.AppendInPlace(item);
  459. if (newRoot != root) {
  460. root = newRoot;
  461. CheckBalance();
  462. }
  463. }
  464. }
  465. /// <summary>
  466. /// Adds an item to the beginning of the BigList. The indices of all existing items
  467. /// in the Deque are increased by one, and the new item has index zero.
  468. /// </summary>
  469. /// <remarks>Adding an item takes, on average, constant time.</remarks>
  470. /// <param name="item">The item to add.</param>
  471. public void AddToFront(T item)
  472. {
  473. if ((uint)Count + 1 > MAXITEMS)
  474. throw new InvalidOperationException(Strings.CollectionTooLarge);
  475. StopEnumerations();
  476. if (root == null)
  477. root = new LeafNode(item);
  478. else {
  479. Node newRoot = root.PrependInPlace(item);
  480. if (newRoot != root) {
  481. root = newRoot;
  482. CheckBalance();
  483. }
  484. }
  485. }
  486. /// <summary>
  487. /// Adds a collection of items to the end of BigList. The indices of all existing items
  488. /// are unchanged. The last item in the added collection becomes the
  489. /// last item in the BigList.
  490. /// </summary>
  491. /// <remarks>This method takes time O(M + log N), where M is the number of items in the
  492. /// <paramref name="collection"/>, and N is the size of the BigList.</remarks>
  493. /// <param name="collection">The collection of items to add.</param>
  494. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  495. public void AddRange(IEnumerable<T> collection)
  496. {
  497. if (collection == null)
  498. throw new ArgumentNullException("collection");
  499. StopEnumerations();
  500. Node node = NodeFromEnumerable(collection);
  501. if (node == null)
  502. return;
  503. else if (root == null) {
  504. root = node;
  505. CheckBalance();
  506. }
  507. else {
  508. if ((uint)Count + (uint)node.count > MAXITEMS)
  509. throw new InvalidOperationException(Strings.CollectionTooLarge);
  510. Node newRoot = root.AppendInPlace(node, true);
  511. if (newRoot != root) {
  512. root = newRoot;
  513. CheckBalance();
  514. }
  515. }
  516. }
  517. /// <summary>
  518. /// Adds a collection of items to the front of BigList. The indices of all existing items
  519. /// in the are increased by the number of items in <paramref name="collection"/>.
  520. /// The first item in the added collection becomes the first item in the BigList.
  521. /// </summary>
  522. /// <remarks>This method takes time O(M + log N), where M is the number of items in the
  523. /// <paramref name="collection"/>, and N is the size of the BigList.</remarks>
  524. /// <param name="collection">The collection of items to add.</param>
  525. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  526. public void AddRangeToFront(IEnumerable<T> collection)
  527. {
  528. if (collection == null)
  529. throw new ArgumentNullException("collection");
  530. StopEnumerations();
  531. Node node = NodeFromEnumerable(collection);
  532. if (node == null)
  533. return;
  534. else if (root == null) {
  535. root = node;
  536. CheckBalance();
  537. }
  538. else {
  539. if ((uint)Count + (uint)node.Count > MAXITEMS)
  540. throw new InvalidOperationException(Strings.CollectionTooLarge);
  541. Node newRoot = root.PrependInPlace(node, true);
  542. if (newRoot != root) {
  543. root = newRoot;
  544. CheckBalance();
  545. }
  546. }
  547. }
  548. /// <summary>
  549. /// Creates a new BigList that is a copy of this list.
  550. /// </summary>
  551. /// <remarks>Copying a BigList takes constant time, and little
  552. /// additional memory, since the storage for the items of the two lists is shared. However, changing
  553. /// either list will take additional time and memory. Portions of the list are copied when they are changed.</remarks>
  554. /// <returns>A copy of the current list</returns>
  555. public BigList<T> Clone()
  556. {
  557. if (root == null)
  558. return new BigList<T>();
  559. else {
  560. root.MarkShared();
  561. return new BigList<T>(root);
  562. }
  563. }
  564. /// <summary>
  565. /// Creates a new BigList that is a copy of this list.
  566. /// </summary>
  567. /// <remarks>Copying a BigList takes constant time, and little
  568. /// additional memory, since the storage for the items of the two lists is shared. However, changing
  569. /// either list will take additional time and memory. Portions of the list are copied when they are changed.</remarks>
  570. /// <returns>A copy of the current list</returns>
  571. object ICloneable.Clone()
  572. {
  573. return Clone();
  574. }
  575. /// <summary>
  576. /// Makes a deep clone of this BigList. A new BigList is created with a clone of
  577. /// each element of this set, by calling ICloneable.Clone on each element. If T is
  578. /// a value type, then this method is the same as Clone.
  579. /// </summary>
  580. /// <remarks><para>If T is a reference type, it must implement
  581. /// ICloneable. Otherwise, an InvalidOperationException is thrown.</para>
  582. /// <para>If T is a reference type, cloning the list takes time approximate O(N), where N is the number of items in the list.</para></remarks>
  583. /// <returns>The cloned set.</returns>
  584. /// <exception cref="InvalidOperationException">T is a reference type that does not implement ICloneable.</exception>
  585. public BigList<T> CloneContents()
  586. {
  587. if (root == null)
  588. return new BigList<T>();
  589. else {
  590. bool itemIsValueType;
  591. if (!Util.IsCloneableType(typeof(T), out itemIsValueType))
  592. throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, typeof(T).FullName));
  593. if (itemIsValueType)
  594. return Clone();
  595. // Create a new list by converting each item in this list via cloning.
  596. return new BigList<T>(Algorithms.Convert<T, T>(this, delegate(T item)
  597. {
  598. if (item == null)
  599. return default(T); // Really null, because we know T is a reference type
  600. else
  601. return (T)(((ICloneable)item).Clone());
  602. }));
  603. }
  604. }
  605. /// <summary>
  606. /// Adds a BigList of items to the end of BigList. The indices of all existing items
  607. /// are unchanged. The last item in <paramref name="list"/> becomes the
  608. /// last item in this list. The added list <paramref name="list"/> is unchanged.
  609. /// </summary>
  610. /// <remarks>This method takes, on average, constant time, regardless of the size
  611. /// of either list. Although conceptually all of the items in <paramref name="list"/> are
  612. /// copied, storage is shared between the two lists until changes are made to the
  613. /// shared sections.</remarks>
  614. /// <param name="list">The list of items to add.</param>
  615. /// <exception cref="ArgumentNullException"><paramref name="list"/> is null.</exception>
  616. public void AddRange(BigList<T> list)
  617. {
  618. if (list == null)
  619. throw new ArgumentNullException("list");
  620. if ((uint)Count + (uint)list.Count > MAXITEMS)
  621. throw new InvalidOperationException(Strings.CollectionTooLarge);
  622. if (list.Count == 0)
  623. return;
  624. StopEnumerations();
  625. if (root == null) {
  626. list.root.MarkShared();
  627. root = list.root;
  628. }
  629. else {
  630. Node newRoot = root.AppendInPlace(list.root, false);
  631. if (newRoot != root) {
  632. root = newRoot;
  633. CheckBalance();
  634. }
  635. }
  636. }
  637. /// <summary>
  638. /// Adds a BigList of items to the front of BigList. The indices of all existing items
  639. /// are increased by the number of items in <paramref name="list"/>. The first item in <paramref name="list"/>
  640. /// becomes the first item in this list. The added list <paramref name="list"/> is unchanged.
  641. /// </summary>
  642. /// <remarks>This method takes, on average, constant time, regardless of the size
  643. /// of either list. Although conceptually all of the items in <paramref name="list"/> are
  644. /// copied, storage is shared between the two lists until changes are made to the
  645. /// shared sections.</remarks>
  646. /// <param name="list">The list of items to add.</param>
  647. /// <exception cref="ArgumentNullException"><paramref name="list"/> is null.</exception>
  648. public void AddRangeToFront(BigList<T> list)
  649. {
  650. if (list == null)
  651. throw new ArgumentNullException("list");
  652. if ((uint)Count + (uint)list.Count > MAXITEMS)
  653. throw new InvalidOperationException(Strings.CollectionTooLarge);
  654. if (list.Count == 0)
  655. return;
  656. StopEnumerations();
  657. if (root == null) {
  658. list.root.MarkShared();
  659. root = list.root;
  660. }
  661. else {
  662. Node newRoot = root.PrependInPlace(list.root, false);
  663. if (newRoot != root) {
  664. root = newRoot;
  665. CheckBalance();
  666. }
  667. }
  668. }
  669. /// <summary>
  670. /// Concatenates two lists together to create a new list. Both lists being concatenated
  671. /// are unchanged. The resulting list contains all the items in <paramref name="first"/>, followed
  672. /// by all the items in <paramref name="second"/>.
  673. /// </summary>
  674. /// <remarks>This method takes, on average, constant time, regardless of the size
  675. /// of either list. Although conceptually all of the items in both lists are
  676. /// copied, storage is shared until changes are made to the
  677. /// shared sections.</remarks>
  678. /// <param name="first">The first list to concatenate.</param>
  679. /// <param name="second">The second list to concatenate.</param>
  680. /// <exception cref="ArgumentNullException"><paramref name="first"/> or <paramref name="second"/> is null.</exception>
  681. public static BigList<T> operator +(BigList<T> first, BigList<T> second)
  682. {
  683. if (first == null)
  684. throw new ArgumentNullException("first");
  685. if (second == null)
  686. throw new ArgumentNullException("second");
  687. if ((uint)first.Count + (uint)second.Count > MAXITEMS)
  688. throw new InvalidOperationException(Strings.CollectionTooLarge);
  689. if (first.Count == 0)
  690. return second.Clone();
  691. else if (second.Count == 0)
  692. return first.Clone();
  693. else {
  694. BigList<T> result = new BigList<T>(first.root.Append(second.root, false));
  695. result.CheckBalance();
  696. return result;
  697. }
  698. }
  699. /// <summary>
  700. /// Creates a new list that contains a subrange of elements from this list. The
  701. /// current list is unchanged.
  702. /// </summary>
  703. /// <remarks>This method takes take O(log N), where N is the size of the current list. Although
  704. /// the sub-range is conceptually copied, storage is shared between the two lists until a change
  705. /// is made to the shared items.</remarks>
  706. /// <remarks>If a view of a sub-range is desired, instead of a copy, use the
  707. /// more efficient <see cref="Range"/> method, which provides a view onto a sub-range of items.</remarks>
  708. /// <param name="index">The starting index of the sub-range.</param>
  709. /// <param name="count">The number of items in the sub-range. If this is zero,
  710. /// the returned list is empty.</param>
  711. /// <returns>A new list with the <paramref name="count"/> items that start at <paramref name="index"/>.</returns>
  712. public BigList<T> GetRange(int index, int count)
  713. {
  714. if (count == 0)
  715. return new BigList<T>();
  716. if (index < 0 || index >= Count)
  717. throw new ArgumentOutOfRangeException("index");
  718. if (count < 0 || count > Count - index)
  719. throw new ArgumentOutOfRangeException("count");
  720. return new BigList<T>(root.Subrange(index, index + count - 1));
  721. }
  722. /// <summary>
  723. /// Returns a view onto a sub-range of this list. Items are not copied; the
  724. /// returned IList&lt;T&gt; is simply a different view onto the same underlying items. Changes to this list
  725. /// are reflected in the view, and vice versa. Insertions and deletions in the view change the size of the
  726. /// view, but insertions and deletions in the underlying list do not.
  727. /// </summary>
  728. /// <remarks>
  729. /// <para>If a copy of the sub-range is desired, use the <see cref="GetRange"/> method instead.</para>
  730. /// <para>This method can be used to apply an algorithm to a portion of a list. For example:</para>
  731. /// <code>Algorithms.ReverseInPlace(list.Range(3, 6))</code>
  732. /// will reverse the 6 items beginning at index 3.</remarks>
  733. /// <param name="index">The starting index of the view.</param>
  734. /// <param name="count">The number of items in the view.</param>
  735. /// <returns>A list that is a view onto the given sub-list. </returns>
  736. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> or <paramref name="count"/> is negative.</exception>
  737. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> + <paramref name="count"/> is greater than the
  738. /// size of this list.</exception>
  739. public sealed override IList<T> Range(int index, int count)
  740. {
  741. if (index < 0 || index > this.Count || (index == this.Count && count != 0))
  742. throw new ArgumentOutOfRangeException("index");
  743. if (count < 0 || count > this.Count || count + index > this.Count)
  744. throw new ArgumentOutOfRangeException("count");
  745. return new BigListRange(this, index, count);
  746. }
  747. /// <summary>
  748. /// Enumerates a range of the items in the list, in order. The item at <paramref name="start"/>
  749. /// is enumerated first, then the next item at index 1, and so on. At most <paramref name="maxItems"/>
  750. /// items are enumerated.
  751. /// </summary>
  752. /// <remarks>Enumerating all of the items in the list take time O(N), where
  753. /// N is the number of items being enumerated. Using GetEnumerator() or foreach
  754. /// is much more efficient than accessing all items by index.</remarks>
  755. /// <param name="start">Index to start enumerating at.</param>
  756. /// <param name="maxItems">Max number of items to enumerate.</param>
  757. /// <returns>An IEnumerator&lt;T&gt; that enumerates all the
  758. /// items in the given range.</returns>
  759. private IEnumerator<T> GetEnumerator(int start, int maxItems)
  760. {
  761. // We could use a recursive enumerator here, but an explicit stack
  762. // is a lot more efficient, and efficiency matters here.
  763. int startStamp = changeStamp; // to detect changes during enumeration.
  764. if (root != null && maxItems > 0) {
  765. ConcatNode[] stack = new ConcatNode[root.Depth];
  766. bool[] leftStack = new bool[root.Depth];
  767. int stackPtr = 0, startIndex = 0;
  768. Node current = root;
  769. LeafNode currentLeaf;
  770. ConcatNode currentConcat;
  771. if (start != 0) {
  772. // Set current to the node containing start, and set startIndex to
  773. // the index within that node.
  774. if (start < 0 || start >= root.Count)
  775. throw new ArgumentOutOfRangeException("start");
  776. currentConcat = current as ConcatNode;
  777. startIndex = start;
  778. while (currentConcat != null) {
  779. stack[stackPtr] = currentConcat;
  780. int leftCount = currentConcat.left.Count;
  781. if (startIndex < leftCount) {
  782. leftStack[stackPtr] = true;
  783. current = currentConcat.left;
  784. }
  785. else {
  786. leftStack[stackPtr] = false;
  787. current = currentConcat.right;
  788. startIndex -= leftCount;
  789. }
  790. ++stackPtr;
  791. currentConcat = current as ConcatNode;
  792. }
  793. }
  794. for (; ; ) {
  795. // If not already at a leaf, walk to the left to find a leaf node.
  796. while ((currentConcat = current as ConcatNode) != null) {
  797. stack[stackPtr] = currentConcat;
  798. leftStack[stackPtr] = true;
  799. ++stackPtr;
  800. current = currentConcat.left;
  801. }
  802. // Iterate the leaf.
  803. currentLeaf = (LeafNode)current;
  804. int limit = currentLeaf.Count;
  805. if (limit > startIndex + maxItems)
  806. limit = startIndex + maxItems;
  807. for (int i = startIndex; i < limit; ++i) {
  808. yield return currentLeaf.items[i];
  809. CheckEnumerationStamp(startStamp);
  810. }
  811. // Update the number of items to interate.
  812. maxItems -= limit - startIndex;
  813. if (maxItems <= 0)
  814. yield break; // Done!
  815. // From now on, start enumerating at 0.
  816. startIndex = 0;
  817. // Go back up the stack until we find a place to the right
  818. // we didn't just come from.
  819. for (; ; ) {
  820. ConcatNode parent;
  821. if (stackPtr == 0)
  822. yield break; // iteration is complete.
  823. parent = stack[--stackPtr];
  824. if (leftStack[stackPtr]) {
  825. leftStack[stackPtr] = false;
  826. ++stackPtr;
  827. current = parent.right;
  828. break;
  829. }
  830. current = parent;
  831. // And keep going up...
  832. }
  833. // current is now a new node we need to visit. Loop around to get it.
  834. }
  835. }
  836. }
  837. /// <summary>
  838. /// Enumerates all of the items in the list, in order. The item at index 0
  839. /// is enumerated first, then the item at index 1, and so on. Usually, the
  840. /// foreach statement is used to call this method implicitly.
  841. /// </summary>
  842. /// <remarks>Enumerating all of the items in the list take time O(N), where
  843. /// N is the number of items in the list. Using GetEnumerator() or foreach
  844. /// is much more efficient than accessing all items by index.</remarks>
  845. /// <returns>An IEnumerator&lt;T&gt; that enumerates all the
  846. /// items in the list.</returns>
  847. public sealed override IEnumerator<T> GetEnumerator()
  848. {
  849. return GetEnumerator(0, int.MaxValue);
  850. }
  851. /// <summary>
  852. /// Given an IEnumerable&lt;T&gt;, create a new Node with all of the
  853. /// items in the enumerable. Returns null if the enumerable has no items.
  854. /// </summary>
  855. /// <param name="collection">The collection to copy.</param>
  856. /// <returns>Returns a Node, not shared or with any shared children,
  857. /// with the items from the collection. If the collection was empty,
  858. /// null is returned.</returns>
  859. private static Node NodeFromEnumerable(IEnumerable<T> collection)
  860. {
  861. Node node = null;
  862. LeafNode leaf;
  863. IEnumerator<T> enumerator = collection.GetEnumerator();
  864. while ((leaf = LeafFromEnumerator(enumerator)) != null) {
  865. if (node == null)
  866. node = leaf;
  867. else {
  868. if ((uint)(node.count) + (uint)(leaf.count) > MAXITEMS)
  869. throw new InvalidOperationException(Strings.CollectionTooLarge);
  870. node = node.AppendInPlace(leaf, true);
  871. }
  872. }
  873. return node;
  874. }
  875. /// <summary>
  876. /// Consumes up to MAXLEAF items from an Enumerator and places them in a leaf
  877. /// node. If the enumerator is at the end, null is returned.
  878. /// </summary>
  879. /// <param name="enumerator">The enumerator to take items from.</param>
  880. /// <returns>A LeafNode with items taken from the enumerator. </returns>
  881. private static LeafNode LeafFromEnumerator(IEnumerator<T> enumerator)
  882. {
  883. int i = 0;
  884. T[] items = null;
  885. while (i < MAXLEAF && enumerator.MoveNext()) {
  886. if (i == 0)
  887. items = new T[MAXLEAF];
  888. if (items!=null)
  889. items[i++] = enumerator.Current;
  890. }
  891. if (items != null)
  892. return new LeafNode(i, items);
  893. else
  894. return null;
  895. }
  896. /// <summary>
  897. /// Create a node that has N copies of the given node.
  898. /// </summary>
  899. /// <param name="copies">Number of copies. Must be non-negative.</param>
  900. /// <param name="node">Node to make copies of.</param>
  901. /// <returns>null if node is null or copies is 0. Otherwise, a node consisting of <paramref name="copies"/> copies
  902. /// of node.</returns>
  903. /// <exception cref="ArgumentOutOfRangeException">copies is negative.</exception>
  904. private static Node NCopiesOfNode(int copies, Node node)
  905. {
  906. if (copies < 0)
  907. throw new ArgumentOutOfRangeException("copies", Strings.ArgMustNotBeNegative);
  908. // Do the simple cases.
  909. if (copies == 0 || node == null)
  910. return null;
  911. if (copies == 1)
  912. return node;
  913. if (copies * (long)(node.count) > MAXITEMS)
  914. throw new InvalidOperationException(Strings.CollectionTooLarge);
  915. // Build up the copies by powers of two.
  916. int n = 1;
  917. Node power = node, builder = null;
  918. while (copies > 0) {
  919. power.MarkShared();
  920. if ((copies & n) != 0) {
  921. // This power of two is used in the final result.
  922. copies -= n;
  923. if (builder == null)
  924. builder = power;
  925. else
  926. builder = builder.Append(power, false);
  927. }
  928. n *= 2;
  929. power = power.Append(power, false);
  930. }
  931. return builder;
  932. }
  933. /// <summary>
  934. /// Check the balance of the current tree and rebalance it if it is more than BALANCEFACTOR
  935. /// levels away from fully balanced. Note that rebalancing a tree may leave it two levels away from
  936. /// fully balanced.
  937. /// </summary>
  938. private void CheckBalance()
  939. {
  940. if (root != null &&
  941. (root.Depth > BALANCEFACTOR && !(root.Depth - BALANCEFACTOR <= MAXFIB && Count >= FIBONACCI[root.Depth - BALANCEFACTOR])))
  942. {
  943. Rebalance();
  944. }
  945. }
  946. /// <summary>
  947. /// Rebalance the current tree. Once rebalanced, the depth of the current tree is no more than
  948. /// two levels from fully balanced, where fully balanced is defined as having Fibonacci(N+2) or more items
  949. /// in a tree of depth N.
  950. /// </summary>
  951. /// <remarks>The rebalancing algorithm is from "Ropes: an Alternative to Strings", by
  952. /// Boehm, Atkinson, and Plass, in SOFTWARE--PRACTICE AND EXPERIENCE, VOL. 25(12), 1315–1330 (DECEMBER 1995).
  953. /// </remarks>
  954. internal void Rebalance()

Large files files are truncated, but you can click here to view the full file