From 7c048d026cfd42706053b364f1f9c99b1e949298 Mon Sep 17 00:00:00 2001 From: sakno Date: Tue, 24 Oct 2023 18:53:57 +0300 Subject: [PATCH] Added operation for moving elements within the span --- src/DotNext.Tests/SpanTests.cs | 41 ++++++++++-- src/DotNext/Span.cs | 119 +++++++++++++++++++++++++++++---- 2 files changed, 141 insertions(+), 19 deletions(-) diff --git a/src/DotNext.Tests/SpanTests.cs b/src/DotNext.Tests/SpanTests.cs index 981d8c842..10147ac17 100644 --- a/src/DotNext.Tests/SpanTests.cs +++ b/src/DotNext.Tests/SpanTests.cs @@ -534,33 +534,62 @@ public static void SwapElements() public static void TransformElements() { // left < right - Span input = stackalloc int[] { 1, 2, 3, 4, 5, 6 }; + Span input = new int[] { 1, 2, 3, 4, 5, 6 }; input.Swap(0..2, 3..6); Equal(new int[] { 4, 5, 6, 3, 1, 2 }, input.ToArray()); // left > right - input = stackalloc int[] { 1, 2, 3, 4, 5, 6 }; + input = new int[] { 1, 2, 3, 4, 5, 6 }; input.Swap(0..3, 4..6); Equal(new int[] { 5, 6, 4, 1, 2, 3 }, input.ToArray()); // left is zero length - input = stackalloc int[] { 1, 2, 3, 4, 5, 6 }; + input = new int[] { 1, 2, 3, 4, 5, 6 }; input.Swap(1..1, 3..6); Equal(new int[] { 1, 4, 5, 6, 2, 3 }, input.ToArray()); + // right is zero length + input = new int[] { 1, 2, 3, 4, 5, 6 }; + input.Swap(0..2, 3..3); + Equal(new int[] { 3, 1, 2, 4, 5, 6 }, input.ToArray()); + // no space between ranges - input = stackalloc int[] { 1, 2, 3, 4, 5, 6 }; + input = new int[] { 1, 2, 3, 4, 5, 6 }; input.Swap(0..2, 2..6); Equal(new int[] { 3, 4, 5, 6, 1, 2 }, input.ToArray()); // left == right - input = stackalloc int[] { 1, 2, 3, 4, 5, 6 }; + input = new int[] { 1, 2, 3, 4, 5, 6 }; input.Swap(0..3, 3..6); Equal(new int[] { 4, 5, 6, 1, 2, 3 }, input.ToArray()); // left and right are empty - input = stackalloc int[] { 1, 2, 3, 4, 5, 6 }; + input = new int[] { 1, 2, 3, 4, 5, 6 }; input.Swap(1..1, 5..5); Equal(new int[] { 1, 2, 3, 4, 5, 6 }, input.ToArray()); + + // overlapping + Throws(() => new int[] { 1, 2, 3, 4, 5, 6 }.AsSpan().Swap(0..2, 1..3)); + } + + [Fact] + public static void MoveRange() + { + // move from left to right + Span input = new int[] { 1, 2, 3, 4, 5, 6 }; + input.Move(0..2, 3); + Equal(new int[] { 3, 1, 2, 4, 5, 6 }, input.ToArray()); + + // move from left to right + input = new int[] { 1, 2, 3, 4, 5, 6 }; + input.Move(1..3, 6); + Equal(new int[] { 1, 4, 5, 6, 2, 3 }, input.ToArray()); + + // move from right to left + input.Move(4..6, 1); + Equal(new int[] { 1, 2, 3, 4, 5, 6 }, input.ToArray()); + + // out of range + Throws(() => new int[] { 1, 2, 3, 4, 5, 6 }.AsSpan().Move(0..2, 1)); } } \ No newline at end of file diff --git a/src/DotNext/Span.cs b/src/DotNext/Span.cs index 8e1a3cf33..511711609 100644 --- a/src/DotNext/Span.cs +++ b/src/DotNext/Span.cs @@ -762,11 +762,15 @@ public static ReadOnlySpan Contravariance(this ReadOnlySpan /// The first span. /// The second span. /// The length of is not of the same length as . + /// overlaps with . public static void Swap(this Span x, Span y) { if (x.Length != y.Length) throw new ArgumentOutOfRangeException(nameof(y)); + if (x.Overlaps(y)) + throw new ArgumentException(ExceptionMessages.OverlappedRange, nameof(y)); + SwapCore(x, y); } @@ -836,27 +840,28 @@ public static void Swap(this Span span, Range range1, Range range2) var (start1, length1) = range1.GetOffsetAndLength(span.Length); var (start2, length2) = range2.GetOffsetAndLength(span.Length); + if (start1 > start2) + { + Intrinsics.Swap(ref start1, ref start2); + Intrinsics.Swap(ref length1, ref length2); + } + + var endOfLeftSegment = start1 + length1; + if (endOfLeftSegment > start2) + throw new ArgumentException(ExceptionMessages.OverlappedRange, nameof(range2)); + if (length1 == length2) { // handle trivial case that allows to avoid allocation of a large buffer Span.SwapCore(span.Slice(start1, length1), span.Slice(start2, length2)); } - else if (start1 < start2) - { - SwapCore(span, start1, length1, start2, length2); - } else { - SwapCore(span, start2, length2, start1, length1); + SwapCore(span, start1, length1, start2, length2, endOfLeftSegment); } - static void SwapCore(Span span, int start1, int length1, int start2, int length2) + static void SwapCore(Span span, int start1, int length1, int start2, int length2, int endOfLeftSegment) { - // check for overlapping - var endOfLeftSegment = start1 + length1; - if (endOfLeftSegment > start2) - throw new ArgumentException(ExceptionMessages.OverlappedRange, nameof(range2)); - Span sourceLarge, sourceSmall, destLarge, @@ -903,12 +908,100 @@ static void SwapCore(Span span, int start1, int length1, int start2, int leng // rearrange elements sourceLarge.CopyTo(buffer.Span); - var spaceBetweenRanges = start2 - endOfLeftSegment; - span.Slice(endOfLeftSegment, spaceBetweenRanges).CopyTo(span.Slice(endOfLeftSegment - shift, spaceBetweenRanges)); + span[endOfLeftSegment..start2].CopyTo(span.Slice(start1 + length2)); sourceSmall.CopyTo(destSmall); buffer.Span.CopyTo(destLarge); buffer.Dispose(); } } + + /// + /// Moves the range within the span to the specified index. + /// + /// The type of the elements in the span. + /// The span of elements to modify. + /// The range of elements within to move. + /// The index of the element before which of elements will be placed. + /// is not a valid index within . + public static void Move(this Span span, Range range, Index destinationIndex) + { + var (sourceIndex, length) = range.GetOffsetAndLength(span.Length); + + if (length is not 0) + MoveCore(span, sourceIndex, destinationIndex.GetOffset(span.Length), length); + + static void MoveCore(Span span, int sourceIndex, int destinationIndex, int length) + { + // prepare buffers + Span source = span.Slice(sourceIndex, length), + destination, + sourceGap, + destinationGap; + if (sourceIndex > destinationIndex) + { + sourceGap = span[destinationIndex..sourceIndex]; + destinationGap = span.Slice(destinationIndex + length); + + destination = span.Slice(destinationIndex, length); + } + else + { + var endOfLeftSegment = sourceIndex + length; + switch (endOfLeftSegment.CompareTo(destinationIndex)) + { + case 0: + return; + case > 0: + throw new ArgumentOutOfRangeException(nameof(destinationIndex)); + case < 0: + sourceGap = span[endOfLeftSegment..destinationIndex]; + destinationGap = span.Slice(sourceIndex); + + destination = span.Slice(destinationIndex - length, length); + break; + } + } + + // Perf: allocate buffer for smallest part of the span + if (source.Length > sourceGap.Length) + { + length = sourceGap.Length; + + var temp = source; + source = sourceGap; + sourceGap = temp; + + temp = destination; + destination = destinationGap; + destinationGap = temp; + } + else + { + Debug.Assert(length == source.Length); + } + + // prepare buffer + MemoryRental buffer; + if (RuntimeHelpers.IsReferenceOrContainsReferences() || length > MemoryRental.StackallocThreshold) + { + buffer = new(length); + } + else + { + unsafe + { + void* bufferPtr = stackalloc byte[checked(Unsafe.SizeOf() * length)]; + buffer = new Span(bufferPtr, length); + } + } + + // rearrange buffers + source.CopyTo(buffer.Span); + sourceGap.CopyTo(destinationGap); + buffer.Span.CopyTo(destination); + + buffer.Dispose(); + } + } } \ No newline at end of file