Free cookie consent management tool by TermsFeed Policy Generator

source: branches/PersistentDataStructures/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Utils/Rope.cs @ 14648

Last change on this file since 14648 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 28.5 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Linq;
21using System.Collections.Generic;
22using System.Diagnostics;
23using System.Globalization;
24using System.Runtime.Serialization;
25using System.Text;
26
27namespace ICSharpCode.AvalonEdit.Utils
28{
29  /// <summary>
30  /// A kind of List&lt;T&gt;, but more efficient for random insertions/removal.
31  /// Also has cheap Clone() and SubRope() implementations.
32  /// </summary>
33  /// <remarks>
34  /// This class is not thread-safe: multiple concurrent write operations or writes concurrent to reads have undefined behaviour.
35  /// Concurrent reads, however, are safe.
36  /// However, clones of a rope are safe to use on other threads even though they share data with the original rope.
37  /// </remarks>
38  [Serializable]
39  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
40  public sealed class Rope<T> : IList<T>, ICloneable
41  {
42    internal RopeNode<T> root;
43   
44    internal Rope(RopeNode<T> root)
45    {
46      this.root = root;
47      root.CheckInvariants();
48    }
49   
50    /// <summary>
51    /// Creates a new rope representing the empty string.
52    /// </summary>
53    public Rope()
54    {
55      // we'll construct the empty rope as a clone of an imaginary static empty rope
56      this.root = RopeNode<T>.emptyRopeNode;
57      root.CheckInvariants();
58    }
59   
60    /// <summary>
61    /// Creates a rope from the specified input.
62    /// This operation runs in O(N).
63    /// </summary>
64    /// <exception cref="ArgumentNullException">input is null.</exception>
65    public Rope(IEnumerable<T> input)
66    {
67      if (input == null)
68        throw new ArgumentNullException("input");
69      Rope<T> inputRope = input as Rope<T>;
70      if (inputRope != null) {
71        // clone ropes instead of copying them
72        inputRope.root.Publish();
73        this.root = inputRope.root;
74      } else {
75        string text = input as string;
76        if (text != null) {
77          // if a string is IEnumerable<T>, then T must be char
78          ((Rope<char>)(object)this).root = CharRope.InitFromString(text);
79        } else {
80          T[] arr = ToArray(input);
81          this.root = RopeNode<T>.CreateFromArray(arr, 0, arr.Length);
82        }
83      }
84      this.root.CheckInvariants();
85    }
86   
87    /// <summary>
88    /// Creates a rope from a part of the array.
89    /// This operation runs in O(N).
90    /// </summary>
91    /// <exception cref="ArgumentNullException">input is null.</exception>
92    public Rope(T[] array, int arrayIndex, int count)
93    {
94      VerifyArrayWithRange(array, arrayIndex, count);
95      this.root = RopeNode<T>.CreateFromArray(array, arrayIndex, count);
96      this.root.CheckInvariants();
97    }
98   
99    /// <summary>
100    /// Creates a new rope that lazily initalizes its content.
101    /// </summary>
102    /// <param name="length">The length of the rope that will be lazily loaded.</param>
103    /// <param name="initializer">
104    /// The callback that provides the content for this rope.
105    /// <paramref name="initializer"/> will be called exactly once when the content of this rope is first requested.
106    /// It must return a rope with the specified length.
107    /// Because the initializer function is not called when a rope is cloned, and such clones may be used on another threads,
108    /// it is possible for the initializer callback to occur on any thread.
109    /// </param>
110    /// <remarks>
111    /// Any modifications inside the rope will also cause the content to be initialized.
112    /// However, insertions at the beginning and the end, as well as inserting this rope into another or
113    /// using the <see cref="Concat(Rope{T},Rope{T})"/> method, allows constructions of larger ropes where parts are
114    /// lazily loaded.
115    /// However, even methods like Concat may sometimes cause the initializer function to be called, e.g. when
116    /// two short ropes are concatenated.
117    /// </remarks>
118    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
119    public Rope(int length, Func<Rope<T>> initializer)
120    {
121      if (initializer == null)
122        throw new ArgumentNullException("initializer");
123      if (length < 0)
124        throw new ArgumentOutOfRangeException("length", length, "Length must not be negative");
125      if (length == 0) {
126        this.root = RopeNode<T>.emptyRopeNode;
127      } else {
128        this.root = new FunctionNode<T>(length, initializer);
129      }
130      this.root.CheckInvariants();
131    }
132   
133    static T[] ToArray(IEnumerable<T> input)
134    {
135      T[] arr = input as T[];
136      return arr ?? input.ToArray();
137    }
138   
139    /// <summary>
140    /// Clones the rope.
141    /// This operation runs in linear time to the number of rope nodes touched since the last clone was created.
142    /// If you count the per-node cost to the operation modifying the rope (doing this doesn't increase the complexity of the modification operations);
143    /// the remainder of Clone() runs in O(1).
144    /// </summary>
145    /// <remarks>
146    /// This method counts as a read access and may be called concurrently to other read accesses.
147    /// </remarks>
148    public Rope<T> Clone()
149    {
150      // The Publish() call actually modifies this rope instance; but this modification is thread-safe
151      // as long as the tree structure doesn't change during the operation.
152      root.Publish();
153      return new Rope<T>(root);
154    }
155   
156    object ICloneable.Clone()
157    {
158      return this.Clone();
159    }
160   
161    /// <summary>
162    /// Resets the rope to an empty list.
163    /// Runs in O(1).
164    /// </summary>
165    public void Clear()
166    {
167      root = RopeNode<T>.emptyRopeNode;
168      OnChanged();
169    }
170   
171    /// <summary>
172    /// Gets the length of the rope.
173    /// Runs in O(1).
174    /// </summary>
175    /// <remarks>
176    /// This method counts as a read access and may be called concurrently to other read accesses.
177    /// </remarks>
178    public int Length {
179      get { return root.length; }
180    }
181   
182    /// <summary>
183    /// Gets the length of the rope.
184    /// Runs in O(1).
185    /// </summary>
186    /// <remarks>
187    /// This method counts as a read access and may be called concurrently to other read accesses.
188    /// </remarks>
189    public int Count {
190      get { return root.length; }
191    }
192   
193    /// <summary>
194    /// Inserts another rope into this rope.
195    /// Runs in O(lg N + lg M), plus a per-node cost as if <c>newElements.Clone()</c> was called.
196    /// </summary>
197    /// <exception cref="ArgumentNullException">newElements is null.</exception>
198    /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception>
199    public void InsertRange(int index, Rope<T> newElements)
200    {
201      if (index < 0 || index > this.Length) {
202        throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture));
203      }
204      if (newElements == null)
205        throw new ArgumentNullException("newElements");
206      newElements.root.Publish();
207      root = root.Insert(index, newElements.root);
208      OnChanged();
209    }
210   
211    /// <summary>
212    /// Inserts new elemetns into this rope.
213    /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements.
214    /// </summary>
215    /// <exception cref="ArgumentNullException">newElements is null.</exception>
216    /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception>
217    public void InsertRange(int index, IEnumerable<T> newElements)
218    {
219      if (newElements == null)
220        throw new ArgumentNullException("newElements");
221      Rope<T> newElementsRope = newElements as Rope<T>;
222      if (newElementsRope != null) {
223        InsertRange(index, newElementsRope);
224      } else {
225        T[] arr = ToArray(newElements);
226        InsertRange(index, arr, 0, arr.Length);
227      }
228    }
229   
230    /// <summary>
231    /// Inserts new elements into this rope.
232    /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements.
233    /// </summary>
234    /// <exception cref="ArgumentNullException">newElements is null.</exception>
235    /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception>
236    public void InsertRange(int index, T[] array, int arrayIndex, int count)
237    {
238      if (index < 0 || index > this.Length) {
239        throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture));
240      }
241      VerifyArrayWithRange(array, arrayIndex, count);
242      if (count > 0) {
243        root = root.Insert(index, array, arrayIndex, count);
244        OnChanged();
245      }
246    }
247   
248    /// <summary>
249    /// Appends multiple elements to the end of this rope.
250    /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements.
251    /// </summary>
252    /// <exception cref="ArgumentNullException">newElements is null.</exception>
253    public void AddRange(IEnumerable<T> newElements)
254    {
255      InsertRange(this.Length, newElements);
256    }
257   
258    /// <summary>
259    /// Appends another rope to the end of this rope.
260    /// Runs in O(lg N + lg M), plus a per-node cost as if <c>newElements.Clone()</c> was called.
261    /// </summary>
262    /// <exception cref="ArgumentNullException">newElements is null.</exception>
263    public void AddRange(Rope<T> newElements)
264    {
265      InsertRange(this.Length, newElements);
266    }
267   
268    /// <summary>
269    /// Appends new elements to the end of this rope.
270    /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements.
271    /// </summary>
272    /// <exception cref="ArgumentNullException">array is null.</exception>
273    public void AddRange(T[] array, int arrayIndex, int count)
274    {
275      InsertRange(this.Length, array, arrayIndex, count);
276    }
277   
278    /// <summary>
279    /// Removes a range of elements from the rope.
280    /// Runs in O(lg N).
281    /// </summary>
282    /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
283    public void RemoveRange(int index, int count)
284    {
285      VerifyRange(index, count);
286      if (count > 0) {
287        root = root.RemoveRange(index, count);
288        OnChanged();
289      }
290    }
291   
292    /// <summary>
293    /// Copies a range of the specified array into the rope, overwriting existing elements.
294    /// Runs in O(lg N + M).
295    /// </summary>
296    public void SetRange(int index, T[] array, int arrayIndex, int count)
297    {
298      VerifyRange(index, count);
299      VerifyArrayWithRange(array, arrayIndex, count);
300      if (count > 0) {
301        root = root.StoreElements(index, array, arrayIndex, count);
302        OnChanged();
303      }
304    }
305   
306    /// <summary>
307    /// Creates a new rope and initializes it with a part of this rope.
308    /// Runs in O(lg N) plus a per-node cost as if <c>this.Clone()</c> was called.
309    /// </summary>
310    /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
311    /// <remarks>
312    /// This method counts as a read access and may be called concurrently to other read accesses.
313    /// </remarks>
314    public Rope<T> GetRange(int index, int count)
315    {
316      VerifyRange(index, count);
317      Rope<T> newRope = Clone();
318      int endIndex = index + count;
319      newRope.RemoveRange(endIndex, newRope.Length - endIndex);
320      newRope.RemoveRange(0, index);
321      return newRope;
322    }
323   
324    /*
325    #region Equality
326    /// <summary>
327    /// Gets whether the two ropes have the same content.
328    /// Runs in O(N + M).
329    /// </summary>
330    /// <remarks>
331    /// This method counts as a read access and may be called concurrently to other read accesses.
332    /// </remarks>
333    public bool Equals(Rope other)
334    {
335      if (other == null)
336        return false;
337      // quick detection for ropes that are clones of each other:
338      if (other.root == this.root)
339        return true;
340      if (other.Length != this.Length)
341        return false;
342      using (RopeTextReader a = new RopeTextReader(this, false)) {
343        using (RopeTextReader b = new RopeTextReader(other, false)) {
344          int charA, charB;
345          do {
346            charA = a.Read();
347            charB = b.Read();
348            if (charA != charB)
349              return false;
350          } while (charA != -1);
351          return true;
352        }
353      }
354    }
355   
356    /// <summary>
357    /// Gets whether two ropes have the same content.
358    /// Runs in O(N + M).
359    /// </summary>
360    public override bool Equals(object obj)
361    {
362      return Equals(obj as Rope);
363    }
364   
365    /// <summary>
366    /// Calculates the hash code of the rope's content.
367    /// Runs in O(N).
368    /// </summary>
369    /// <remarks>
370    /// This method counts as a read access and may be called concurrently to other read accesses.
371    /// </remarks>
372    public override int GetHashCode()
373    {
374      int hashcode = 0;
375      using (RopeTextReader reader = new RopeTextReader(this, false)) {
376        unchecked {
377          int val;
378          while ((val = reader.Read()) != -1) {
379            hashcode = hashcode * 31 + val;
380          }
381        }
382      }
383      return hashcode;
384    }
385    #endregion
386     */
387   
388    /// <summary>
389    /// Concatenates two ropes. The input ropes are not modified.
390    /// Runs in O(lg N + lg M).
391    /// </summary>
392    /// <remarks>
393    /// This method counts as a read access and may be called concurrently to other read accesses.
394    /// </remarks>
395    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
396    public static Rope<T> Concat(Rope<T> left, Rope<T> right)
397    {
398      if (left == null)
399        throw new ArgumentNullException("left");
400      if (right == null)
401        throw new ArgumentNullException("right");
402      left.root.Publish();
403      right.root.Publish();
404      return new Rope<T>(RopeNode<T>.Concat(left.root, right.root));
405    }
406   
407    /// <summary>
408    /// Concatenates multiple ropes. The input ropes are not modified.
409    /// </summary>
410    /// <remarks>
411    /// This method counts as a read access and may be called concurrently to other read accesses.
412    /// </remarks>
413    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
414    public static Rope<T> Concat(params Rope<T>[] ropes)
415    {
416      if (ropes == null)
417        throw new ArgumentNullException("ropes");
418      Rope<T> result = new Rope<T>();
419      foreach (Rope<T> r in ropes)
420        result.AddRange(r);
421      return result;
422    }
423   
424    #region Caches / Changed event
425    internal struct RopeCacheEntry
426    {
427      internal readonly RopeNode<T> node;
428      internal readonly int nodeStartIndex;
429     
430      internal RopeCacheEntry(RopeNode<T> node, int nodeStartOffset)
431      {
432        this.node = node;
433        this.nodeStartIndex = nodeStartOffset;
434      }
435     
436      internal bool IsInside(int offset)
437      {
438        return offset >= nodeStartIndex && offset < nodeStartIndex + node.length;
439      }
440    }
441   
442    // cached pointer to 'last used node', used to speed up accesses by index that are close together
443    [NonSerialized]
444    volatile ImmutableStack<RopeCacheEntry> lastUsedNodeStack;
445   
446    internal void OnChanged()
447    {
448      lastUsedNodeStack = null;
449     
450      root.CheckInvariants();
451    }
452    #endregion
453   
454    #region GetChar / SetChar
455    /// <summary>
456    /// Gets/Sets a single character.
457    /// Runs in O(lg N) for random access. Sequential read-only access benefits from a special optimization and runs in amortized O(1).
458    /// </summary>
459    /// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to Length-1).</exception>
460    /// <remarks>
461    /// The getter counts as a read access and may be called concurrently to other read accesses.
462    /// </remarks>
463    public T this[int index] {
464      get {
465        // use unsigned integers - this way negative values for index overflow and can be tested for with the same check
466        if (unchecked((uint)index >= (uint)this.Length)) {
467          throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture));
468        }
469        RopeCacheEntry entry = FindNodeUsingCache(index).PeekOrDefault();
470        return entry.node.contents[index - entry.nodeStartIndex];
471      }
472      set {
473        if (index < 0 || index >= this.Length) {
474          throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture));
475        }
476        root = root.SetElement(index, value);
477        OnChanged();
478        /* Here's a try at implementing the setter using the cached node stack (UNTESTED code!).
479         * However I don't use the code because it's complicated and doesn't integrate correctly with change notifications.
480         * Instead, I'll use the much easier to understand recursive solution.
481         * Oh, and it also doesn't work correctly with function nodes.
482        ImmutableStack<RopeCacheEntry> nodeStack = FindNodeUsingCache(offset);
483        RopeCacheEntry entry = nodeStack.Peek();
484        if (!entry.node.isShared) {
485          entry.node.contents[offset - entry.nodeStartOffset] = value;
486          // missing: clear the caches except for the node stack cache (e.g. ToString() cache?)
487        } else {
488          RopeNode oldNode = entry.node;
489          RopeNode newNode = oldNode.Clone();
490          newNode.contents[offset - entry.nodeStartOffset] = value;
491          for (nodeStack = nodeStack.Pop(); !nodeStack.IsEmpty; nodeStack = nodeStack.Pop()) {
492            RopeNode parentNode = nodeStack.Peek().node;
493            RopeNode newParentNode = parentNode.CloneIfShared();
494            if (newParentNode.left == oldNode) {
495              newParentNode.left = newNode;
496            } else {
497              Debug.Assert(newParentNode.right == oldNode);
498              newParentNode.right = newNode;
499            }
500            if (parentNode == newParentNode) {
501              // we were able to change the existing node (it was not shared);
502              // there's no reason to go further upwards
503              ClearCacheOnModification();
504              return;
505            } else {
506              oldNode = parentNode;
507              newNode = newParentNode;
508            }
509          }
510          // we reached the root of the rope.
511          Debug.Assert(root == oldNode);
512          root = newNode;
513          ClearCacheOnModification();
514        }*/
515      }
516    }
517   
518    internal ImmutableStack<RopeCacheEntry> FindNodeUsingCache(int index)
519    {
520      Debug.Assert(index >= 0 && index < this.Length);
521     
522      // thread safety: fetch stack into local variable
523      ImmutableStack<RopeCacheEntry> stack = lastUsedNodeStack;
524      ImmutableStack<RopeCacheEntry> oldStack = stack;
525     
526      if (stack == null) {
527        stack = ImmutableStack<RopeCacheEntry>.Empty.Push(new RopeCacheEntry(root, 0));
528      }
529      while (!stack.PeekOrDefault().IsInside(index))
530        stack = stack.Pop();
531      while (true) {
532        RopeCacheEntry entry = stack.PeekOrDefault();
533        // check if we've reached a leaf or function node
534        if (entry.node.height == 0) {
535          if (entry.node.contents == null) {
536            // this is a function node - go down into its subtree
537            entry = new RopeCacheEntry(entry.node.GetContentNode(), entry.nodeStartIndex);
538            // entry is now guaranteed NOT to be another function node
539          }
540          if (entry.node.contents != null) {
541            // this is a node containing actual content, so we're done
542            break;
543          }
544        }
545        // go down towards leaves
546        if (index - entry.nodeStartIndex >= entry.node.left.length)
547          stack = stack.Push(new RopeCacheEntry(entry.node.right, entry.nodeStartIndex + entry.node.left.length));
548        else
549          stack = stack.Push(new RopeCacheEntry(entry.node.left, entry.nodeStartIndex));
550      }
551     
552      // write back stack to volatile cache variable
553      // (in multithreaded access, it doesn't matter which of the threads wins - it's just a cache)
554      if (oldStack != stack) {
555        // no need to write when we the cache variable didn't change
556        lastUsedNodeStack = stack;
557      }
558     
559      // this method guarantees that it finds a leaf node
560      Debug.Assert(stack.Peek().node.contents != null);
561      return stack;
562    }
563    #endregion
564   
565    #region ToString / WriteTo
566    internal void VerifyRange(int startIndex, int length)
567    {
568      if (startIndex < 0 || startIndex > this.Length) {
569        throw new ArgumentOutOfRangeException("startIndex", startIndex, "0 <= startIndex <= " + this.Length.ToString(CultureInfo.InvariantCulture));
570      }
571      if (length < 0 || startIndex + length > this.Length) {
572        throw new ArgumentOutOfRangeException("length", length, "0 <= length, startIndex(" + startIndex + ")+length <= " + this.Length.ToString(CultureInfo.InvariantCulture));
573      }
574    }
575   
576    internal static void VerifyArrayWithRange(T[] array, int arrayIndex, int count)
577    {
578      if (array == null)
579        throw new ArgumentNullException("array");
580      if (arrayIndex < 0 || arrayIndex > array.Length) {
581        throw new ArgumentOutOfRangeException("startIndex", arrayIndex, "0 <= arrayIndex <= " + array.Length.ToString(CultureInfo.InvariantCulture));
582      }
583      if (count < 0 || arrayIndex + count > array.Length) {
584        throw new ArgumentOutOfRangeException("count", count, "0 <= length, arrayIndex(" + arrayIndex + ")+count <= " + array.Length.ToString(CultureInfo.InvariantCulture));
585      }
586    }
587   
588    /// <summary>
589    /// Creates a string from the rope. Runs in O(N).
590    /// </summary>
591    /// <returns>A string consisting of all elements in the rope as comma-separated list in {}.
592    /// As a special case, Rope&lt;char&gt; will return its contents as string without any additional separators or braces,
593    /// so it can be used like StringBuilder.ToString().</returns>
594    /// <remarks>
595    /// This method counts as a read access and may be called concurrently to other read accesses.
596    /// </remarks>
597    public override string ToString()
598    {
599      Rope<char> charRope = this as Rope<char>;
600      if (charRope != null) {
601        return charRope.ToString(0, this.Length);
602      } else {
603        StringBuilder b = new StringBuilder();
604        foreach (T element in this) {
605          if (b.Length == 0)
606            b.Append('{');
607          else
608            b.Append(", ");
609          b.Append(element.ToString());
610        }
611        b.Append('}');
612        return b.ToString();
613      }
614    }
615   
616    internal string GetTreeAsString()
617    {
618      #if DEBUG
619      return root.GetTreeAsString();
620      #else
621      return "Not available in release build.";
622      #endif
623    }
624    #endregion
625   
626    bool ICollection<T>.IsReadOnly {
627      get { return false; }
628    }
629   
630    /// <summary>
631    /// Finds the first occurance of item.
632    /// Runs in O(N).
633    /// </summary>
634    /// <returns>The index of the first occurance of item, or -1 if it cannot be found.</returns>
635    /// <remarks>
636    /// This method counts as a read access and may be called concurrently to other read accesses.
637    /// </remarks>
638    public int IndexOf(T item)
639    {
640      return IndexOf(item, 0, this.Length);
641    }
642   
643    /// <summary>
644    /// Gets the index of the first occurrence the specified item.
645    /// </summary>
646    /// <param name="item">Item to search for.</param>
647    /// <param name="startIndex">Start index of the search.</param>
648    /// <param name="count">Length of the area to search.</param>
649    /// <returns>The first index where the item was found; or -1 if no occurrence was found.</returns>
650    /// <remarks>
651    /// This method counts as a read access and may be called concurrently to other read accesses.
652    /// </remarks>
653    public int IndexOf(T item, int startIndex, int count)
654    {
655      VerifyRange(startIndex, count);
656     
657      while (count > 0) {
658        var entry = FindNodeUsingCache(startIndex).PeekOrDefault();
659        T[] contents = entry.node.contents;
660        int startWithinNode = startIndex - entry.nodeStartIndex;
661        int nodeLength = Math.Min(entry.node.length, startWithinNode + count);
662        int r = Array.IndexOf(contents, item, startWithinNode, nodeLength - startWithinNode);
663        if (r >= 0)
664          return entry.nodeStartIndex + r;
665        count -= nodeLength - startWithinNode;
666        startIndex = entry.nodeStartIndex + nodeLength;
667      }
668      return -1;
669    }
670   
671    /// <summary>
672    /// Gets the index of the last occurrence of the specified item in this rope.
673    /// </summary>
674    public int LastIndexOf(T item)
675    {
676      return LastIndexOf(item, 0, this.Length);
677    }
678   
679    /// <summary>
680    /// Gets the index of the last occurrence of the specified item in this rope.
681    /// </summary>
682    /// <param name="item">The search item</param>
683    /// <param name="startIndex">Start index of the area to search.</param>
684    /// <param name="count">Length of the area to search.</param>
685    /// <returns>The last index where the item was found; or -1 if no occurrence was found.</returns>
686    /// <remarks>The search proceeds backwards from (startIndex+count) to startIndex.
687    /// This is different than the meaning of the parameters on Array.LastIndexOf!</remarks>
688    public int LastIndexOf(T item, int startIndex, int count)
689    {
690      VerifyRange(startIndex, count);
691     
692      var comparer = EqualityComparer<T>.Default;
693      for (int i = startIndex + count - 1; i >= startIndex; i--) {
694        if (comparer.Equals(this[i], item))
695          return i;
696      }
697      return -1;
698    }
699   
700    /// <summary>
701    /// Inserts the item at the specified index in the rope.
702    /// Runs in O(lg N).
703    /// </summary>
704    public void Insert(int index, T item)
705    {
706      InsertRange(index, new[] { item }, 0, 1);
707    }
708   
709    /// <summary>
710    /// Removes a single item from the rope.
711    /// Runs in O(lg N).
712    /// </summary>
713    public void RemoveAt(int index)
714    {
715      RemoveRange(index, 1);
716    }
717   
718    /// <summary>
719    /// Appends the item at the end of the rope.
720    /// Runs in O(lg N).
721    /// </summary>
722    public void Add(T item)
723    {
724      InsertRange(this.Length, new[] { item }, 0, 1);
725    }
726   
727    /// <summary>
728    /// Searches the item in the rope.
729    /// Runs in O(N).
730    /// </summary>
731    /// <remarks>
732    /// This method counts as a read access and may be called concurrently to other read accesses.
733    /// </remarks>
734    public bool Contains(T item)
735    {
736      return IndexOf(item) >= 0;
737    }
738   
739    /// <summary>
740    /// Copies the whole content of the rope into the specified array.
741    /// Runs in O(N).
742    /// </summary>
743    /// <remarks>
744    /// This method counts as a read access and may be called concurrently to other read accesses.
745    /// </remarks>
746    public void CopyTo(T[] array, int arrayIndex)
747    {
748      CopyTo(0, array, arrayIndex, this.Length);
749    }
750   
751    /// <summary>
752    /// Copies the a part of the rope into the specified array.
753    /// Runs in O(lg N + M).
754    /// </summary>
755    /// <remarks>
756    /// This method counts as a read access and may be called concurrently to other read accesses.
757    /// </remarks>
758    public void CopyTo(int index, T[] array, int arrayIndex, int count)
759    {
760      VerifyRange(index, count);
761      VerifyArrayWithRange(array, arrayIndex, count);
762      this.root.CopyTo(index, array, arrayIndex, count);
763    }
764   
765    /// <summary>
766    /// Removes the first occurance of an item from the rope.
767    /// Runs in O(N).
768    /// </summary>
769    public bool Remove(T item)
770    {
771      int index = IndexOf(item);
772      if (index >= 0) {
773        RemoveAt(index);
774        return true;
775      }
776      return false;
777    }
778   
779    /// <summary>
780    /// Retrieves an enumerator to iterate through the rope.
781    /// The enumerator will reflect the state of the rope from the GetEnumerator() call, further modifications
782    /// to the rope will not be visible to the enumerator.
783    /// </summary>
784    /// <remarks>
785    /// This method counts as a read access and may be called concurrently to other read accesses.
786    /// </remarks>
787    public IEnumerator<T> GetEnumerator()
788    {
789      this.root.Publish();
790      return Enumerate(root);
791    }
792   
793    /// <summary>
794    /// Creates an array and copies the contents of the rope into it.
795    /// Runs in O(N).
796    /// </summary>
797    /// <remarks>
798    /// This method counts as a read access and may be called concurrently to other read accesses.
799    /// </remarks>
800    public T[] ToArray()
801    {
802      T[] arr = new T[this.Length];
803      this.root.CopyTo(0, arr, 0, arr.Length);
804      return arr;
805    }
806   
807    /// <summary>
808    /// Creates an array and copies the contents of the rope into it.
809    /// Runs in O(N).
810    /// </summary>
811    /// <remarks>
812    /// This method counts as a read access and may be called concurrently to other read accesses.
813    /// </remarks>
814    public T[] ToArray(int startIndex, int count)
815    {
816      VerifyRange(startIndex, count);
817      T[] arr = new T[count];
818      CopyTo(startIndex, arr, 0, count);
819      return arr;
820    }
821   
822    static IEnumerator<T> Enumerate(RopeNode<T> node)
823    {
824      Stack<RopeNode<T>> stack = new Stack<RopeNode<T>>();
825      while (node != null) {
826        // go to leftmost node, pushing the right parts that we'll have to visit later
827        while (node.contents == null) {
828          if (node.height == 0) {
829            // go down into function nodes
830            node = node.GetContentNode();
831            continue;
832          }
833          Debug.Assert(node.right != null);
834          stack.Push(node.right);
835          node = node.left;
836        }
837        // yield contents of leaf node
838        for (int i = 0; i < node.length; i++) {
839          yield return node.contents[i];
840        }
841        // go up to the next node not visited yet
842        if (stack.Count > 0)
843          node = stack.Pop();
844        else
845          node = null;
846      }
847    }
848   
849    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
850    {
851      return this.GetEnumerator();
852    }
853  }
854}
Note: See TracBrowser for help on using the repository browser.