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

Move range #584

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
129 changes: 102 additions & 27 deletions Source/SuperLinq/Move.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,30 @@ public static partial class SuperEnumerable
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// </remarks>
public static IEnumerable<T> Move<T>(this IEnumerable<T> source, int fromIndex, int count, int toIndex)
public static IEnumerable<T> Move<T>(
this IEnumerable<T> source,
int fromIndex,
int count,
int toIndex
)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentOutOfRangeException.ThrowIfNegative(fromIndex);
ArgumentOutOfRangeException.ThrowIfNegative(count);
ArgumentOutOfRangeException.ThrowIfNegative(toIndex);

return
toIndex == fromIndex || count == 0
? source :
toIndex < fromIndex
? Core(source, toIndex, fromIndex - toIndex, count)
: Core(source, fromIndex, count, toIndex - fromIndex);
return toIndex == fromIndex || count == 0
viceroypenguin marked this conversation as resolved.
Show resolved Hide resolved
? source
: toIndex < fromIndex
? Core(source, toIndex, fromIndex - toIndex, count)
: Core(source, fromIndex, count, toIndex - fromIndex);

static IEnumerable<T> Core(IEnumerable<T> source, int bufferStartIndex, int bufferSize, int bufferYieldIndex)
static IEnumerable<T> Core(
IEnumerable<T> source,
int bufferStartIndex,
int bufferSize,
int bufferYieldIndex
)
{
var hasMore = true;
bool MoveNext(IEnumerator<T> e) => hasMore && (hasMore = e.MoveNext());
Expand Down Expand Up @@ -112,32 +121,98 @@ public static IEnumerable<T> Move<T>(this IEnumerable<T> source, Range range, In
startIndex = range.Start.GetOffset(count);
endIndex = range.End.GetOffset(count);
toIndex = to.GetOffset(count);
}
else
yield return (T)Move(source, startIndex, endIndex - startIndex, toIndex);
}
else
{
using var e = source.GetEnumerator();
if (!e.MoveNext())
{
yield break;
}
count = 1;
while (e.MoveNext())
switch ((range.Start.IsFromEnd, range.End.IsFromEnd, to.IsFromEnd))
viceroypenguin marked this conversation as resolved.
Show resolved Hide resolved
{
count++;
case (false, false, true):
using (var e = source.GetEnumerator())
{
if (!e.MoveNext())
{
yield break;
}

var bufferCap = to.Value;
var moveCap = range.End.Value - range.Start.Value;
var buffer = new Queue<T>(bufferCap);
var move = new Queue<T>(moveCap);

buffer.Enqueue(e.Current);
count = 1;

while (e.MoveNext())
{
buffer.Enqueue(e.Current);
checked
{
++count;
}
if (count > to.Value)
{
var idx = count - bufferCap;
if (idx > range.Start.Value && idx <= range.End.Value)
{
move.Enqueue(buffer.Dequeue());
}
else
{
yield return buffer.Dequeue();
}
}
}
while (move.TryDequeue(out var element))
{
yield return element;
}
while (buffer.TryDequeue(out var element))
{
yield return element;
}
}
yield break;
case (false, true, false):
// [4, 5, 2, 4, 1, §, 5] Move(1..^4, 2)
break;
case (false, true, true):
// [4, 5, 2, 4, 1, §, 5] Move(1..^4, ^2)
break;
case (true, false, false):
// [4, 5, 2, 4, 1, §, 5] Move(^5..4, 2)
break;
case (true, false, true):
// [4, 5, 2, 4, 1, §, 5] Move(^5..4, ^2)
break;
case (true, true, false):
if (range.End.Value > range.Start.Value)
{
yield break;
}
// [4, 5, 2, 4, 1, §, 5] Move(^5..^2, 4)
// Cannot yield any elements until count is known.
// Once count is known, can proceed to yield elements
break;
case (true, true, true):
// [4, 5, 2, 4, 1, §, 5] Move(^5..^3, ^2)
break;
}
startIndex = range.Start.Value;
endIndex = range.End.Value;
toIndex = to.GetOffset(count);
}
foreach (var e in Move(source, startIndex, endIndex - startIndex, toIndex))
}
else
{
foreach (
var e in Move(
source,
range.Start.Value,
range.End.Value - range.Start.Value,
to.Value
)
)
{
yield return e;
}
yield break;
}
foreach (var e in Move(source, range.Start.Value, range.End.Value - range.Start.Value, to.Value))
{
yield return e;
}
}
}
52 changes: 30 additions & 22 deletions Tests/SuperLinq.Test/MoveTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ public class MoveTest
[Fact]
public void MoveWithNegativeFromIndex()
{
_ = Assert.Throws<ArgumentOutOfRangeException>(() =>
new[] { 1 }.Move(-1, 0, 0));
_ = Assert.Throws<ArgumentOutOfRangeException>(() => new[] { 1 }.Move(-1, 0, 0));
}

[Fact]
Expand All @@ -18,29 +17,25 @@ public void MoveRangeWithNegativeStartIndex()
[Fact]
public void MoveWithNegativeCount()
{
_ = Assert.Throws<ArgumentOutOfRangeException>(() =>
new[] { 1 }.Move(0, -1, 0));
_ = Assert.Throws<ArgumentOutOfRangeException>(() => new[] { 1 }.Move(0, -1, 0));
}

[Fact]
public void MoveRangeWithDecendingRange()
{
_ = Assert.Throws<ArgumentOutOfRangeException>(() =>
new[] { 1 }.Move(0..-1, 0));
_ = Assert.Throws<ArgumentOutOfRangeException>(() => new[] { 1 }.Move(0..-1, 0));
}

[Fact]
public void MoveWithNegativeToIndex()
{
_ = Assert.Throws<ArgumentOutOfRangeException>(() =>
new[] { 1 }.Move(0, 0, -1));
_ = Assert.Throws<ArgumentOutOfRangeException>(() => new[] { 1 }.Move(0, 0, -1));
}

[Fact]
public void MoveRangeWithNegativeToIndex()
{
_ = Assert.Throws<ArgumentOutOfRangeException>(() =>
new[] { 1 }.Move(0..0, -1));
_ = Assert.Throws<ArgumentOutOfRangeException>(() => new[] { 1 }.Move(0..0, -1));
}

[Fact]
Expand Down Expand Up @@ -82,8 +77,7 @@ public void MoveRange(int length, Range range, int toIndex)
public static IEnumerable<object[]> MoveSource()
{
const int Length = 10;
return
from index in Enumerable.Range(0, Length)
return from index in Enumerable.Range(0, Length)
from count in Enumerable.Range(0, Length + 1)
from tcd in new object[][]
{
Expand All @@ -96,8 +90,7 @@ from count in Enumerable.Range(0, Length + 1)
public static IEnumerable<object[]> MoveRangeSource()
{
const int Length = 10;
return
from index in Enumerable.Range(0, Length)
return from index in Enumerable.Range(0, Length)
from count in Enumerable.Range(0, Length + 1)
from tcd in new object[][]
{
Expand All @@ -108,15 +101,22 @@ from count in Enumerable.Range(0, Length + 1)
}

[Theory, MemberData(nameof(MoveWithSequenceShorterThanToIndexSource))]
public void MoveWithSequenceShorterThanToIndex(int length, int fromIndex, int count, int toIndex)
public void MoveWithSequenceShorterThanToIndex(
int length,
int fromIndex,
int count,
int toIndex
)
{
var source = Enumerable.Range(0, length);

using var test = source.AsTestingSequence();

var result = test.Move(fromIndex, count, toIndex);

var expectations = source.Exclude(fromIndex, count).Concat(source.Take(fromIndex..(fromIndex + count)));
var expectations = source
.Exclude(fromIndex, count)
.Concat(source.Take(fromIndex..(fromIndex + count)));
Assert.Equal(expectations, result);
}

Expand All @@ -129,17 +129,17 @@ public void MoveRangeWithSequenceShorterThanToIndex(int length, Range range, int

var result = test.Move(range, toIndex);

var expectations = source.Exclude(range.Start.Value, range.End.Value - range.Start.Value).Concat(source.Take(range));
var expectations = source
.Exclude(range.Start.Value, range.End.Value - range.Start.Value)
.Concat(source.Take(range));
Assert.Equal(expectations, result);
}

public static IEnumerable<object[]> MoveWithSequenceShorterThanToIndexSource() =>
Enumerable.Range(10, 10 + 5)
.Select(toIndex => new object[] { 10, 5, 2, toIndex, });
Enumerable.Range(10, 10 + 5).Select(toIndex => new object[] { 10, 5, 2, toIndex, });

public static IEnumerable<object[]> MoveRangeWithSequenceShorterThanToIndexSource() =>
Enumerable.Range(10, 10 + 5)
.Select(toIndex => new object[] { 10, 5..7, toIndex, });
Enumerable.Range(10, 10 + 5).Select(toIndex => new object[] { 10, 5..7, toIndex, });

[Fact]
public void MoveIsRepeatable()
Expand Down Expand Up @@ -194,4 +194,12 @@ public void MoveRngeWithCountEqualsZero()
var result = source.Move(5..5, 999);
result.AssertSequenceEqual(Enumerable.Range(0, 10));
}
}

[Fact]
public void MoveRangeFromEndIndex()
{
using var source = Enumerable.Range(0, 8).AsTestingSequence();
var result = source.Move(1..4, ^3);
result.AssertSequenceEqual([0, 4, 1, 2, 3, 5, 6, 7]);
}
}