Example #1
0
        public void TestRemove()
        {
            List <int> list = new List <int> {
                1, 2, 3
            };
            Window <int>         window         = new Window <int>(list, 1);
            ReadOnlyWindow <int> readOnlyWindow = new ReadOnlyWindow <int>(list, 1);

            int[] l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(2, window.Count);
            Assert.AreEqual(3, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(2, readOnlyWindow.Count);
            Assert.AreEqual(3, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Remove through window
            window.Remove(2);
            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(1, window.Count);
            Assert.AreEqual(2, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(1, readOnlyWindow.Count);
            Assert.AreEqual(2, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Remove from underlying list (note that the offset doesn't change, the window is effectively moved).
            list.Remove(1);
            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(0, window.Count);
            Assert.AreEqual(1, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(0, readOnlyWindow.Count);
            Assert.AreEqual(1, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Remove all items!
            list.Clear();

            Assert.AreEqual(0, window.Count);
            Assert.AreEqual(0, window.Length);
            Assert.AreEqual(1, window.Offset); // Note the offset is beyond the end of the underlying data
            Assert.AreEqual(0, readOnlyWindow.Count);
            Assert.AreEqual(0, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset); // Note the offset is beyond the end of the underlying data
        }
Example #2
0
        public void TestLengthImplicitZeroOffsetOne()
        {
            List<int> list = new List<int> { 1 };
            Window<int> window = new Window<int>(list, 1);
            ReadOnlyWindow<int> readOnlyWindow = new ReadOnlyWindow<int>(list, 1);

            Assert.AreEqual(0, window.Count);
            Assert.AreEqual(1, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(0, readOnlyWindow.Count);
            Assert.AreEqual(1, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);
        }
Example #3
0
        public void TestLengthZero()
        {
            List <int>           list           = new List <int>();
            Window <int>         window         = list;
            ReadOnlyWindow <int> readOnlyWindow = list;

            Assert.AreEqual(0, window.Count);
            Assert.AreEqual(0, window.Length);
            Assert.AreEqual(0, window.Offset);
            Assert.AreEqual(0, readOnlyWindow.Count);
            Assert.AreEqual(0, readOnlyWindow.Length);
            Assert.AreEqual(0, readOnlyWindow.Offset);
        }
Example #4
0
 /// <summary>
 /// Initializes a new instance of the <see cref="Chunk{T}" /> class.
 /// </summary>
 /// <param name="areEqual">if set to <see langword="true" /> <paramref name="a"/> and <paramref name="b"/> are considered equal.</param>
 /// <param name="a">The "A" list.</param>
 /// <param name="b">The "B" list.</param>
 /// <exception cref="System.ArgumentNullException"></exception>
 /// <exception cref="ArgumentNullException"></exception>
 internal Chunk(
     bool areEqual,
     [CanBeNull] ReadOnlyWindow <T> a,
     [CanBeNull] ReadOnlyWindow <T> b)
 {
     if (a == null && b == null)
     {
         throw new ArgumentNullException(nameof(a));
     }
     AreEqual = areEqual;
     A        = a;
     B        = b;
 }
Example #5
0
        public void TestInsert()
        {
            List <int> list = new List <int> {
                1, 2, 3
            };
            Window <int>         window         = new Window <int>(list, 1);
            ReadOnlyWindow <int> readOnlyWindow = new ReadOnlyWindow <int>(list, 1);

            int[] l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(2, window.Count);
            Assert.AreEqual(3, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(2, readOnlyWindow.Count);
            Assert.AreEqual(3, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Insert through window
            window.Insert(1, 4);
            Assert.AreEqual(4, list[2]);
            Assert.AreEqual(window[1], list[2]);

            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(3, window.Count);
            Assert.AreEqual(4, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(3, readOnlyWindow.Count);
            Assert.AreEqual(4, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Insert through underlying data
            list.Insert(3, 5);
            Assert.AreEqual(5, list[3]);
            Assert.AreEqual(window[2], list[3]);

            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(4, window.Count);
            Assert.AreEqual(5, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(4, readOnlyWindow.Count);
            Assert.AreEqual(5, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);
        }
Example #6
0
        public void TestLengthExplicitZeroOffsetOne()
        {
            List <int> list = new List <int> {
                1, 2
            };
            Window <int>         window         = new Window <int>(list, 1, 0);
            ReadOnlyWindow <int> readOnlyWindow = new ReadOnlyWindow <int>(list, 1, 0);

            Assert.AreEqual(0, window.Count);
            Assert.AreEqual(2, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(0, readOnlyWindow.Count);
            Assert.AreEqual(2, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);
        }
Example #7
0
        public void TestImplicitCastPreservesDataLink()
        {
            List <int> list = new List <int> {
                1, 2, 3, 4
            };
            Window <int>         window         = list;
            ReadOnlyWindow <int> readOnlyWindow = window;

            CollectionAssert.AreEqual(list, window);
            CollectionAssert.AreEqual(list, readOnlyWindow);

            // Check that modifications are reflected in the windows.
            window[1] = 5;
            Assert.AreEqual(5, list[1]);
            Assert.AreEqual(window[1], list[1]);
            CollectionAssert.AreEqual(list, window);
            CollectionAssert.AreEqual(list, readOnlyWindow);
        }
Example #8
0
        /// <summary>
        /// Initializes a new instance of the <see cref="Differences{T}" /> class.
        /// </summary>
        /// <param name="a">The "A" list.</param>
        /// <param name="offsetA">The offset to the start of a window in the first collection.</param>
        /// <param name="lengthA">The length of the window in the first collection.</param>
        /// <param name="b">The "B" list.</param>
        /// <param name="offsetB">The offset to the start of a window in the second collection.</param>
        /// <param name="lengthB">The length of the window in the second collection.</param>
        /// <param name="comparer">The comparer to compare items in each collection.</param>
        /// <exception cref="ArgumentNullException"><paramref name="a" /> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="b" /> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="comparer" /> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="offsetA" /> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="lengthA" /> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="offsetB" /> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="lengthB" /> is out of range.</exception>
        /// <exception cref="Exception">The <paramref name="comparer"/> throws an exception.</exception>
        internal Differences(
            [NotNull] IReadOnlyList <T> a,
            int offsetA,
            int lengthA,
            [NotNull] IReadOnlyList <T> b,
            int offsetB,
            int lengthB,
            [NotNull] Func <T, T, bool> comparer)
        {
            if (a == null)
            {
                throw new ArgumentNullException(nameof(a));
            }
            if (b == null)
            {
                throw new ArgumentNullException(nameof(b));
            }
            if (comparer == null)
            {
                throw new ArgumentNullException(nameof(comparer));
            }
            A = new ReadOnlyWindow <T>(a, offsetA, lengthA);
            B = new ReadOnlyWindow <T>(b, offsetB, lengthB);

            List <Chunk <T> > chunks = new List <Chunk <T> >();

            bool[] modificationsA = new bool[A.Count];
            bool[] modificationsB = new bool[B.Count];

            int max        = A.Count + B.Count + 1;
            int vectorSize = 2 * max + 2;

            // Vector for the (0,0) to (x,y) search
            int[] downVector = new int[vectorSize];
            // Vector for the (u,v) to (N,M) search
            int[] upVector = new int[vectorSize];

            Stack <int, int, int, int> stack = new Stack <int, int, int, int>();

            stack.Push(0, A.Count, 0, B.Count);

            int lowerA;
            int upperA;
            int lowerB;
            int upperB;

            while (stack.TryPop(out lowerA, out upperA, out lowerB, out upperB))
            {
                // Skip equal lines at start of chunk
                while (lowerA < upperA && lowerB < upperB && comparer(A[lowerA], B[lowerB]))
                {
                    lowerA++;
                    lowerB++;
                }

                // Skip equal lines at end of the chunk.
                while (lowerA < upperA && lowerB < upperB && comparer(A[upperA - 1], B[upperB - 1]))
                {
                    --upperA;
                    --upperB;
                }

                if (lowerA == upperA)
                {
                    // Everything left in the second collection is not in the first collection.
                    while (lowerB < upperB)
                    {
                        modificationsB[lowerB++] = true;
                    }
                    continue;
                }
                if (lowerB == upperB)
                {
                    // Everything left in the first collection is not in the second collection.
                    while (lowerA < upperA)
                    {
                        modificationsA[lowerA++] = true;
                    }
                    continue;
                }

                /*
                 * Find where to cut A & B for shortest middle snake.
                 */

                // The k-line to start the forward search
                int downK = lowerA - lowerB;
                // The k-line to start the reverse search
                int upK = upperA - upperB;

                // Difference in collection sizes at this point.
                int delta = (upperA - lowerA) - (upperB - lowerB);

                // Whether the delta is an odd number.
                bool oddDelta = (delta & 1) != 0;

                // The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based
                // and are access using a specific offset: upOffset for upVector and downOffset for downVector
                int downOffset = max - downK;
                int upOffset   = max - upK;

                int maxD = ((upperA - lowerA + upperB - lowerB) / 2) + 1;

                // Initialize vectors
                downVector[downOffset + downK + 1] = lowerA;
                upVector[upOffset + upK - 1]       = upperA;

                int d    = 0;
                int cutA = -1;
                int cutB = -1;
                do
                {
                    // Extend the forward path.
                    int downStart = downK - d;
                    int downEnd   = downK + d;
                    int upStart   = upK - d;
                    int upEnd     = upK + d;
                    int k         = downStart;
                    do
                    {
                        // Find the only or better starting point
                        int x;
                        int offsetK = downOffset + k;
                        if (k == downStart)
                        {
                            // Step down
                            x = downVector[offsetK + 1];
                        }
                        else
                        {
                            // Step right
                            x = downVector[offsetK - 1] + 1;
                            if ((k < downEnd) &&
                                (downVector[offsetK + 1] >= x))
                            {
                                // Step down
                                x = downVector[offsetK + 1];
                            }
                        }
                        int y = x - k;

                        // Find the end of the furthest reaching forward D-path in diagonal k.
                        while ((x < upperA) &&
                               (y < upperB) &&
                               (comparer(A[x], B[y])))
                        {
                            x++;
                            y++;
                        }
                        downVector[offsetK] = x;

                        // Check for overlap
                        if (oddDelta &&
                            (upStart < k) &&
                            (k < upEnd) &&
                            upVector[upOffset + k] <= downVector[offsetK])
                        {
                            cutA = downVector[offsetK];
                            cutB = downVector[offsetK] - k;
                            break;
                        }
                        k += 2;
                    } while (k <= downEnd);

                    // Once we find the cut points we're done.
                    if (cutA > -1)
                    {
                        break;
                    }

                    // Extend the reverse path.
                    k = upStart;
                    do
                    {
                        // Find the only or better starting point
                        int x;
                        int offsetK = upOffset + k;
                        if (k == upEnd)
                        {
                            // Step up
                            x = upVector[offsetK - 1];
                        }
                        else
                        {
                            // Step left
                            x = upVector[offsetK + 1] - 1;
                            if ((k > upStart) &&
                                (upVector[offsetK - 1] < x))
                            {
                                // Step up
                                x = upVector[offsetK - 1];
                            }
                        }
                        int y = x - k;

                        while ((x > lowerA) &&
                               (y > lowerB) &&
                               (comparer(A[x - 1], B[y - 1])))
                        {
                            x--;
                            y--;
                        }
                        upVector[offsetK] = x;

                        // Check for overlap
                        if (!oddDelta &&
                            (downStart <= k) &&
                            (k <= downEnd) &&
                            upVector[offsetK] <= downVector[downOffset + k])
                        {
                            cutA = downVector[downOffset + k];
                            cutB = downVector[downOffset + k] - k;
                            break;
                        }
                        k += 2;
                    } while (k <= upEnd);
                    d++;
                } while (d <= maxD && cutA < 0);

                // Now that we have the cut points, add to stack and try again.
                stack.Push(lowerA, cutA, lowerB, cutB);
                stack.Push(cutA, upperA, cutB, upperB);
            }

            /*
             * Build chunks
             */
            int itemA  = 0;
            int itemB  = 0;
            int startA = 0;
            int startB = 0;
            ReadOnlyWindow <T> subSetA;
            ReadOnlyWindow <T> subSetB;

            while (itemA < A.Count || itemB < B.Count)
            {
                // Scan through unchanged items.
                if ((itemA < A.Count) && (!modificationsA[itemA]) &&
                    (itemB < B.Count) && (!modificationsB[itemB]))
                {
                    itemA++;
                    itemB++;
                    continue;
                }

                // Output any equal data.
                if (itemA > startA)
                {
                    // The length of A & B should be identical!
                    Debug.Assert(itemB - startB == itemA - startA);
                    subSetA = A.GetSubset(startA, itemA - startA);
                    subSetB = B.GetSubset(startB, itemB - startB);
                    chunks.Add(new Chunk <T>(true, subSetA, subSetB));
                }

                startA = itemA;
                startB = itemB;

                while (itemA < A.Count &&
                       (itemB >= B.Count || modificationsA[itemA]))
                {
                    itemA++;
                }

                while (itemB < B.Count &&
                       (itemA >= A.Count || modificationsB[itemB]))
                {
                    itemB++;
                }

                subSetA = itemA > startA?A.GetSubset(startA, itemA - startA) : null;

                subSetB = itemB > startB?B.GetSubset(startB, itemB - startB) : null;

                if (subSetA != null || subSetB != null)
                {
                    chunks.Add(new Chunk <T>(false, subSetA, subSetB));
                }

                startA = itemA;
                startB = itemB;
            }

            // Output any remaining data
            if (itemA > startA)
            {
                // The length of A & B should be identical!
                Debug.Assert(itemB - startB == itemA - startA);
                subSetA = A.GetSubset(startA, itemA - startA);
                subSetB = B.GetSubset(startB, itemB - startB);
                chunks.Add(new Chunk <T>(true, subSetA, subSetB));
            }

            _chunks = chunks;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="StringDifferences" /> class.
        /// </summary>
        /// <param name="a">The 'A' string.</param>
        /// <param name="offsetA">The offset to the start of a window in the first string.</param>
        /// <param name="lengthA">The length of the window in the first string.</param>
        /// <param name="b">The 'B' string.</param>
        /// <param name="offsetB">The offset to the start of a window in the second string.</param>
        /// <param name="lengthB">The length of the window in the second string.</param>
        /// <param name="textOptions">The text options.</param>
        /// <param name="comparer">The character comparer.</param>
        /// <exception cref="ArgumentNullException"><paramref name="a" /> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="b" /> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="comparer" /> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="offsetA" /> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="lengthA" /> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="offsetB" /> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="lengthB" /> is out of range.</exception>
        /// <exception cref="Exception">The <paramref name="comparer" /> throws an exception.</exception>
        internal StringDifferences(
            [NotNull] string a,
            int offsetA,
            int lengthA,
            [NotNull] string b,
            int offsetB,
            int lengthB,
            TextOptions textOptions,
            [NotNull] Func <char, char, bool> comparer)
        {
            if (a == null)
            {
                throw new ArgumentNullException(nameof(a));
            }
            if (b == null)
            {
                throw new ArgumentNullException(nameof(b));
            }
            if (comparer == null)
            {
                throw new ArgumentNullException(nameof(comparer));
            }
            A = a;
            B = b;

            if (textOptions != TextOptions.None)
            {
                // Wrap the comparer with an additional check to handle special characters.
                Func <char, char, bool> oc = comparer;
                if (textOptions.HasFlag(TextOptions.IgnoreWhiteSpace))
                {
                    // Ignore white space - treat all whitespace as the same (note this will handle line endings too).
                    comparer = (x, y) => char.IsWhiteSpace(x) ? char.IsWhiteSpace(y) : oc(x, y);
                }
                else if (textOptions.HasFlag(TextOptions.NormalizeLineEndings))
                {
                    // Just normalize line endings - treat '\r' and '\n\ as the same
                    comparer = (x, y) => x == '\r' || x == '\n' ? y == '\r' || y == '\n' : oc(x, y);
                }
            }

            // Map strings based on text options
            StringMap aMap = a.ToMapped(textOptions);
            StringMap bMap = b.ToMapped(textOptions);

            // Perform diff on mapped string
            Differences <char> chunks = aMap.Diff(bMap, comparer);

            // Special case simple equality
            if (chunks.Count < 2)
            {
                Chunk <char> chunk = chunks.Single();
                // ReSharper disable once PossibleNullReferenceException
                _chunks = new[] { new StringChunk(chunk.AreEqual, a, 0, b, 0) };
                return;
            }

            // To reverse the mapping we first calculate the split points in the original strings, and find
            // the last reference to the original strings in each chunk.
            int[] aEnds = new int[chunks.Count];
            int[] bEnds = new int[chunks.Count];
            int   lastA = 0;
            int   lastB = 0;

            for (int i = 0; i < chunks.Count; i++)
            {
                Chunk <char>          chunk  = chunks[i];
                ReadOnlyWindow <char> chunkA = chunk.A;
                ReadOnlyWindow <char> chunkB = chunk.B;
                if (chunk.A != null)
                {
                    aEnds[i] = aMap.GetOriginalIndex(chunkA.Offset + chunkA.Count - 1) + 1;
                    lastA    = i;
                }
                else
                {
                    aEnds[i] = -1;
                }

                if (chunk.B != null)
                {
                    bEnds[i] = bMap.GetOriginalIndex(chunkB.Offset + chunkB.Count - 1) + 1;
                    lastB    = i;
                }
                else
                {
                    bEnds[i] = -1;
                }
            }

            // Now we're ready to build up a new chunk array based on the original strings
            StringChunk[] stringChunks = new StringChunk[chunks.Count];
            int           aStart       = 0;
            int           bStart       = 0;

            for (int i = 0; i < chunks.Count; i++)
            {
                int aEnd = i == lastA ? aMap.OriginalCount : aEnds[i];
                int bEnd = i == lastB ? bMap.OriginalCount : bEnds[i];

                string ac = aEnd > -1 ? a.Substring(aStart, aEnd - aStart) : null;
                string bc = bEnd > -1 ? b.Substring(bStart, bEnd - bStart) : null;

                stringChunks[i] = new StringChunk(chunks[i].AreEqual, ac, aEnd > -1 ? aStart : -1, bc, bEnd > -1 ? bStart : -1);

                if (aEnd > -1)
                {
                    aStart = aEnd;
                }
                if (bEnd > -1)
                {
                    bStart = bEnd;
                }
            }

            _chunks = stringChunks;
        }
Example #10
0
        public void TestRemove()
        {
            List<int> list = new List<int> { 1, 2, 3 };
            Window<int> window = new Window<int>(list, 1);
            ReadOnlyWindow<int> readOnlyWindow = new ReadOnlyWindow<int>(list, 1);

            int[] l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(2, window.Count);
            Assert.AreEqual(3, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(2, readOnlyWindow.Count);
            Assert.AreEqual(3, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Remove through window
            window.Remove(2);
            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(1, window.Count);
            Assert.AreEqual(2, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(1, readOnlyWindow.Count);
            Assert.AreEqual(2, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Remove from underlying list (note that the offset doesn't change, the window is effectively moved).
            list.Remove(1);
            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(0, window.Count);
            Assert.AreEqual(1, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(0, readOnlyWindow.Count);
            Assert.AreEqual(1, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Remove all items!
            list.Clear();

            Assert.AreEqual(0, window.Count);
            Assert.AreEqual(0, window.Length);
            Assert.AreEqual(1, window.Offset); // Note the offset is beyond the end of the underlying data
            Assert.AreEqual(0, readOnlyWindow.Count);
            Assert.AreEqual(0, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset); // Note the offset is beyond the end of the underlying data
        }
Example #11
0
        public void TestInsert()
        {
            List<int> list = new List<int> { 1, 2, 3 };
            Window<int> window = new Window<int>(list, 1);
            ReadOnlyWindow<int> readOnlyWindow = new ReadOnlyWindow<int>(list, 1);

            int[] l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(2, window.Count);
            Assert.AreEqual(3, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(2, readOnlyWindow.Count);
            Assert.AreEqual(3, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Insert through window
            window.Insert(1, 4);
            Assert.AreEqual(4, list[2]);
            Assert.AreEqual(window[1], list[2]);

            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(3, window.Count);
            Assert.AreEqual(4, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(3, readOnlyWindow.Count);
            Assert.AreEqual(4, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);

            // Insert through underlying data
            list.Insert(3, 5);
            Assert.AreEqual(5, list[3]);
            Assert.AreEqual(window[2], list[3]);

            l = list.Skip(1).ToArray();
            CollectionAssert.AreEqual(l, window);
            CollectionAssert.AreEqual(l, readOnlyWindow);

            Assert.AreEqual(4, window.Count);
            Assert.AreEqual(5, window.Length);
            Assert.AreEqual(1, window.Offset);
            Assert.AreEqual(4, readOnlyWindow.Count);
            Assert.AreEqual(5, readOnlyWindow.Length);
            Assert.AreEqual(1, readOnlyWindow.Offset);
        }