Skip to content

Commit

Permalink
feat: add IList<T>.Rotate and Span<T>.Rotate
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverbooth committed Nov 17, 2024
1 parent 51e7d00 commit 87090da
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## 5.0.0 - [Unreleased]

### Added

- X1OD: Added `IList<T>.Rotate(int)`.
- X1OD: Added `Span<T>.Rotate(int)`.

### Changed

- X10D: Removed `IEnumerable<T>.GreatestCommonFactor` for all integer types in favour of generic math.
Expand Down
40 changes: 40 additions & 0 deletions X10D.Tests/src/Collections/ListTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,46 @@ public void RemoveRange_ShouldRemoveElements_GivenList()
Assert.That(list, Is.EqualTo(new[] { 1, 2, 7, 8, 9, 10 }).AsCollection);
}

[Test]
public void Rotate_ShouldShiftElements_ByNegativeShiftAmount()
{
int[] array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
int[] expected = [5, 6, 7, 8, 9, 10, 1, 2, 3, 4];

array.Rotate(-4);

Assert.That(array, Is.EqualTo(expected).AsCollection);
}

[Test]
public void Rotate_ShouldShiftElements_ByPositiveShiftAmount()
{
int[] array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
int[] expected = [7, 8, 9, 10, 1, 2, 3, 4, 5, 6];

array.Rotate(4);

Assert.That(array, Is.EqualTo(expected).AsCollection);
}

[Test]
public void Rotate_ShouldNotShiftElements_WithShift0()
{
int[] array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
int[] expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

array.Rotate(0);

Assert.That(array, Is.EqualTo(expected).AsCollection);
}

[Test]
public void Rotate_ShouldThrowArgumentNullException_GivenNullSource()
{
int[] array = null!;
Assert.Throws<ArgumentNullException>(() => array.Rotate(0));
}

[Test]
public void Shuffle_ShouldReorder_GivenNotNull()
{
Expand Down
34 changes: 34 additions & 0 deletions X10D.Tests/src/Collections/SpanTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NUnit.Framework;
using X10D.Collections;
#if !NET9_0_OR_GREATER
using X10D.Collections;

Check warning on line 4 in X10D.Tests/src/Collections/SpanTest.cs

View workflow job for this annotation

GitHub Actions / Build & Test

The using directive for 'X10D.Collections' appeared previously in this namespace

Check warning on line 4 in X10D.Tests/src/Collections/SpanTest.cs

View workflow job for this annotation

GitHub Actions / Build & Test

The using directive for 'X10D.Collections' appeared previously in this namespace
#endif
Expand Down Expand Up @@ -32,6 +33,39 @@ public void Replace_ShouldDoNothing_GivenSpanWithNoMatchingElements()
Assert.That(span.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 2, 5, 2, 7, 2, 9, 2, 11, 2, 13, 2, 15, 2 }));
}

[Test]
public void Rotate_ShouldShiftElements_ByNegativeShiftAmount()
{
Span<int> array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Span<int> expected = [5, 6, 7, 8, 9, 10, 1, 2, 3, 4];

array.Rotate(-4);

Assert.That(array.ToArray(), Is.EqualTo(expected.ToArray()).AsCollection);
}

[Test]
public void Rotate_ShouldShiftElements_ByPositiveShiftAmount()
{
Span<int> array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Span<int> expected = [7, 8, 9, 10, 1, 2, 3, 4, 5, 6];

array.Rotate(4);

Assert.That(array.ToArray(), Is.EqualTo(expected.ToArray()).AsCollection);
}

[Test]
public void Rotate_ShouldNotShiftElements_WithShift0()
{
Span<int> array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Span<int> expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

array.Rotate(0);

Assert.That(array.ToArray(), Is.EqualTo(expected.ToArray()).AsCollection);
}

#if !NET9_0_OR_GREATER
[Test]
public void Split_OnEmptySpan_ShouldYieldNothing_UsingCharDelimiter_GivenReadOnlySpan()
Expand Down
37 changes: 37 additions & 0 deletions X10D/src/Collections/ListExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics.Contracts;
using X10D.Core;
using X10D.Math;

#pragma warning disable CA5394

Expand Down Expand Up @@ -249,6 +250,42 @@ public static void RemoveRange<T>(this IList<T> source, Range range)
}
}

/// <summary>
/// Shifts the elements of the current list by a specified amount, wrapping them in the process.
/// </summary>
/// <param name="source">The list of elements to shift.</param>
/// <param name="shift">The amount to shift.</param>
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
public static void Rotate<T>(this IList<T> source, int shift)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

if (shift == 0)
{
return;
}

shift = shift.Mod(source.Count);
Reverse(source, 0, source.Count - 1);
Reverse(source, 0, shift - 1);
Reverse(source, shift, source.Count - 1);
return;

static void Reverse(IList<T> list, int start, int end)
{
while (start < end)
{
(list[start], list[end]) = (list[end], list[start]);
start++;
end--;
}
}
}

/// <summary>
/// Reorganizes the elements in a list by implementing a Fisher-Yates shuffle.
/// </summary>
Expand Down
33 changes: 33 additions & 0 deletions X10D/src/Collections/SpanExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using X10D.Math;

namespace X10D.Collections;

/// <summary>
Expand All @@ -16,6 +18,37 @@ public static ReadOnlySpan<T> AsReadOnly<T>(this in Span<T> source)
return source;
}

/// <summary>
/// Shifts the elements of the current span by a specified amount, wrapping them in the process.
/// </summary>
/// <param name="source">The span of elements to shift.</param>
/// <param name="shift">The amount to shift.</param>
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
public static void Rotate<T>(this Span<T> source, int shift)
{
if (shift == 0)
{
return;
}

shift = shift.Mod(source.Length);
Reverse(source, 0, source.Length - 1);
Reverse(source, 0, shift - 1);
Reverse(source, shift, source.Length - 1);
return;

static void Reverse(Span<T> span, int start, int end)
{
while (start < end)
{
(span[start], span[end]) = (span[end], span[start]);
start++;
end--;
}
}
}

#if !NET9_0_OR_GREATER
/// <summary>
/// Splits a span of elements into sub-spans based on a delimiting element.
Expand Down

0 comments on commit 87090da

Please sign in to comment.