Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ReadOnlySequence<T>.GetPosition(SequencePosition) #771

Merged
merged 12 commits into from
Aug 17, 2020
5 changes: 3 additions & 2 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ public static void Reverse<T>(this System.Span<T> span) { }
public static bool SequenceEqual<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other) where T : System.IEquatable<T> { throw null; }
public static bool SequenceEqual<T>(this System.Span<T> span, System.ReadOnlySpan<T> other) where T : System.IEquatable<T> { throw null; }
public static void Sort<T>(this System.Span<T> span) { }
public static void Sort<T, TComparer>(this System.Span<T> span, TComparer comparer) where TComparer : System.Collections.Generic.IComparer<T>? { }
public static void Sort<T>(this System.Span<T> span, System.Comparison<T> comparison) { }
public static void Sort<TKey, TValue>(this System.Span<TKey> keys, System.Span<TValue> items) { }
public static void Sort<TKey, TValue, TComparer>(this System.Span<TKey> keys, System.Span<TValue> items, TComparer comparer) where TComparer : System.Collections.Generic.IComparer<TKey>? { }
public static void Sort<TKey, TValue>(this System.Span<TKey> keys, System.Span<TValue> items, System.Comparison<TKey> comparison) { }
public static void Sort<T, TComparer>(this System.Span<T> span, TComparer comparer) where TComparer : System.Collections.Generic.IComparer<T>? { }
public static void Sort<TKey, TValue, TComparer>(this System.Span<TKey> keys, System.Span<TValue> items, TComparer comparer) where TComparer : System.Collections.Generic.IComparer<TKey>? { }
public static bool StartsWith(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> value, System.StringComparison comparisonType) { throw null; }
public static bool StartsWith<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
public static bool StartsWith<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
Expand Down Expand Up @@ -219,6 +219,7 @@ public readonly partial struct ReadOnlySequence<T>
public long Length { get { throw null; } }
public System.SequencePosition Start { get { throw null; } }
public System.Buffers.ReadOnlySequence<T>.Enumerator GetEnumerator() { throw null; }
public long GetOffset(System.SequencePosition position) { throw null; }
public System.SequencePosition GetPosition(long offset) { throw null; }
public System.SequencePosition GetPosition(long offset, System.SequencePosition origin) { throw null; }
public System.Buffers.ReadOnlySequence<T> Slice(int start, int length) { throw null; }
Expand Down
58 changes: 58 additions & 0 deletions src/libraries/System.Memory/src/System/Buffers/ReadOnlySequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,64 @@ public SequencePosition GetPosition(long offset)
return Seek(offset);
}

/// <summary>
/// Returns the offset of a <paramref name="position" /> within this sequence from the start.
/// </summary>
/// <param name="position">The <see cref="System.SequencePosition"/> of which to get the offset.</param>
/// <returns>The offset from the start of the sequence.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">The position is out of range.</exception>
public long GetOffset(SequencePosition position)
{
object? positionSequenceObject = position.GetObject();
bool positionIsNull = positionSequenceObject == null;
BoundsCheck(position, !positionIsNull);

object? startObject = _startObject;
object? endObject = _endObject;

uint positionIndex = (uint)GetIndex(position);

// if sequence object is null we suppose start segment
if (positionIsNull)
{
positionSequenceObject = _startObject;
positionIndex = (uint)GetIndex(_startInteger);
}

// Single-Segment Sequence
if (startObject == endObject)
{
return positionIndex;
}
else
{
// Verify position validity, this is not covered by BoundsCheck for Multi-Segment Sequence
// BoundsCheck for Multi-Segment Sequence check only validity inside current sequence but not for SequencePosition validity.
// For single segment position bound check is implicit.
Debug.Assert(positionSequenceObject != null);

if (((ReadOnlySequenceSegment<T>)positionSequenceObject).Memory.Length - positionIndex < 0)
Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Feb 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahsonkhan I don't know if this is ok, default BoundCheck method doesn't check for SequencePosition validity, but only if offset specified is valid against current ROSequence.
It's implicit for single segment because the object is the same(if sequence position is pertinent) and only indexes are checked.

ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();

// Multi-Segment Sequence
ReadOnlySequenceSegment<T>? currentSegment = (ReadOnlySequenceSegment<T>?)startObject;
while (currentSegment != null && currentSegment != positionSequenceObject)
{
currentSegment = currentSegment.Next!;
}

// Hit the end of the segments but didn't find the segment
if (currentSegment is null)
{
ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
}

Debug.Assert(currentSegment!.RunningIndex + positionIndex >= 0);

return currentSegment!.RunningIndex + positionIndex;
}
}

/// <summary>
/// Returns a new <see cref="SequencePosition"/> at an <paramref name="offset"/> from the <paramref name="origin"/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,200 @@ public void CheckEndReachableDoesNotCrossPastEnd()

#endregion

#region Offset

[Fact]
public void GetOffset_SingleSegment()
{
var buffer = new ReadOnlySequence<T>(new T[50]);
Assert.Equal(25, buffer.GetOffset(buffer.GetPosition(25)));
}

private (BufferSegment<T> bufferSegment1, BufferSegment<T> bufferSegment4) GetBufferSegment()
{
// [50] -> [50] -> [0] -> [50]
var bufferSegment1 = new BufferSegment<T>(new T[50]);
BufferSegment<T> bufferSegment2 = bufferSegment1.Append(new T[50]);
BufferSegment<T> bufferSegment3 = bufferSegment2.Append(new T[0]);
return (bufferSegment1, bufferSegment3.Append(new T[50]));
}

private ReadOnlySequence<T> GetFourSegmentsReadOnlySequence()
{
(BufferSegment<T> bufferSegment1, BufferSegment<T> bufferSegment4) = GetBufferSegment();
return new ReadOnlySequence<T>(bufferSegment1, 0, bufferSegment4, 50);
}

[Fact]
public void GetOffset_MultiSegment_FirstSegment()
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
Assert.Equal(25, buffer.GetOffset(buffer.GetPosition(25)));
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void GetOffset_MultiSegment_LastSegment()
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
Assert.Equal(125, buffer.GetOffset(buffer.GetPosition(125)));
}

[Fact]
public void GetOffset_MultiSegment_MiddleSegment()
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
Assert.Equal(75, buffer.GetOffset(buffer.GetPosition(75)));
}

[Fact]
public void GetOffset_SingleSegment_NullPositionObject()
{
var buffer = new ReadOnlySequence<T>(new T[50]);
Assert.Equal(0, buffer.GetOffset(new SequencePosition(null, 25)));
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void GetOffset_MultiSegment_NullPositionObject()
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
Assert.Equal(0, buffer.GetOffset(new SequencePosition(null, 25)));
}

[Fact]
public void GetOffset_MultiSegment_BoundaryConditions()
{
// [50] -> [50] -> [0] -> [50]
var bufferSegment1 = new BufferSegment<T>(new T[50]);
BufferSegment<T> bufferSegment2 = bufferSegment1.Append(new T[50]);
BufferSegment<T> bufferSegment3 = bufferSegment2.Append(new T[0]);
BufferSegment<T> bufferSegment4 = bufferSegment3.Append(new T[50]);
var sequence = new ReadOnlySequence<T>(bufferSegment1, 0, bufferSegment4, 50);

// Non empty adjacent segment
Assert.Equal(50, sequence.GetOffset(new SequencePosition(bufferSegment1, 50)));
Assert.Equal(50, sequence.GetOffset(new SequencePosition(bufferSegment2, 0)));
Assert.Equal(51, sequence.GetOffset(new SequencePosition(bufferSegment2, 1)));

// Empty adjacent segment
Assert.Equal(100, sequence.GetOffset(new SequencePosition(bufferSegment2, 50)));
Assert.Equal(100, sequence.GetOffset(new SequencePosition(bufferSegment3, 0)));
Assert.Equal(101, sequence.GetOffset(new SequencePosition(bufferSegment4, 1)));

// Cannot get 101 starting from empty adjacent segment
Assert.Throws<ArgumentOutOfRangeException>(() => sequence.GetOffset(new SequencePosition(bufferSegment3, 1)));
}

[Fact]
public void GetOffset_MultiSegment_Enumerate()
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
for (int i = 0; i <= buffer.Length; i++)
{
Assert.Equal(i, buffer.GetOffset(buffer.GetPosition(i)));
}
}

[Fact]
public void GetOffset_SingleSegment_Enumerate()
{
ReadOnlySequence<T> buffer = new ReadOnlySequence<T>(new T[50]);
for (int i = 0; i <= buffer.Length; i++)
{
Assert.Equal(i, buffer.GetOffset(buffer.GetPosition(i)));
}
}

[Fact]
public void GetOffset_MultiSegment_Slice()
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
for (int i = 0; i <= buffer.Length; i++)
{
Assert.Equal(buffer.Slice(0, i).Length, buffer.GetOffset(buffer.GetPosition(i)));
}
}

[Fact]
public void GetOffset_SingleSegment_Slice()
{
ReadOnlySequence<T> buffer = new ReadOnlySequence<T>(new T[50]);
for (int i = 0; i <= buffer.Length; i++)
{
Assert.Equal(buffer.Slice(0, i).Length, buffer.GetOffset(buffer.GetPosition(i)));
}
}

[Fact]
public void GetOffset_SingleSegment_PositionOutOfRange()
{
var positionObject = new T[50];
var buffer = new ReadOnlySequence<T>(positionObject);
Assert.Throws<ArgumentOutOfRangeException>("position", () => buffer.GetOffset(new SequencePosition(positionObject, 75)));
}

[Fact]
public void GetOffset_MultiSegment_PositionOutOfRange()
{
(BufferSegment<T> bufferSegment1, BufferSegment<T> bufferSegment4) = GetBufferSegment();
var buffer = new ReadOnlySequence<T>(bufferSegment1, 0, bufferSegment4, 50);
Assert.Throws<ArgumentOutOfRangeException>("position", () => buffer.GetOffset(new SequencePosition(bufferSegment4, 200)));
}

[Fact]
public void GetOffset_MultiSegment_PositionOutOfRange_SegmentNotFound()
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
ReadOnlySequence<T> buffer2 = GetFourSegmentsReadOnlySequence();
Assert.Throws<ArgumentOutOfRangeException>("position", () => buffer.GetOffset(buffer2.GetPosition(25)));
}

[Fact]
public void GetOffset_MultiSegment_InvalidSequencePositionSegment()
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
{
ReadOnlySequence<T> buffer = GetFourSegmentsReadOnlySequence();
ReadOnlySequence<T> buffer2 = new ReadOnlySequence<T>(new T[50]);
Assert.Throws<InvalidCastException>(() => buffer.GetOffset(buffer2.GetPosition(25)));
}

[Fact]
public void GetOffset_SingleSegment_SequencePositionSegment()
{
var data = new T[0];
var sequence = new ReadOnlySequence<T>(data);
Assert.Equal(0, sequence.GetOffset(new SequencePosition(data, 0)));

// Invalid positions
Assert.Throws<ArgumentOutOfRangeException>(() => sequence.GetOffset(new SequencePosition(data, 1)));
}

[Fact]
public void GetOffset_MultiSegment_SequencePositionSegment()
{
// [0] -> [0] -> [0] -> [50]
var bufferSegment1 = new BufferSegment<T>(new T[0]);
BufferSegment<T> bufferSegment2 = bufferSegment1.Append(new T[0]);
BufferSegment<T> bufferSegment3 = bufferSegment2.Append(new T[0]);
BufferSegment<T> bufferSegment4 = bufferSegment3.Append(new T[50]);

var sequence = new ReadOnlySequence<T>(bufferSegment1, 0, bufferSegment4, 50);

Assert.Equal(0, sequence.GetOffset(new SequencePosition(bufferSegment1, 0)));
Assert.Equal(0, sequence.GetOffset(new SequencePosition(bufferSegment2, 0)));
Assert.Equal(0, sequence.GetOffset(new SequencePosition(bufferSegment3, 0)));
Assert.Equal(0, sequence.GetOffset(new SequencePosition(bufferSegment4, 0)));

// Invalid positions
Assert.Throws<ArgumentOutOfRangeException>(() => sequence.GetOffset(new SequencePosition(bufferSegment1, 1)));
Assert.Throws<ArgumentOutOfRangeException>(() => sequence.GetOffset(new SequencePosition(bufferSegment2, 1)));
Assert.Throws<ArgumentOutOfRangeException>(() => sequence.GetOffset(new SequencePosition(bufferSegment3, 1)));

for (int i = 0; i <= bufferSegment4.Memory.Length; i++)
{
Assert.Equal(i, sequence.GetOffset(new SequencePosition(bufferSegment4, i)));
}
}
#endregion

#region First

[Fact]
Expand Down