Skip to content

Commit 87090da

Browse files
committed
feat: add IList<T>.Rotate and Span<T>.Rotate
1 parent 51e7d00 commit 87090da

File tree

5 files changed

+149
-0
lines changed

5 files changed

+149
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## 5.0.0 - [Unreleased]
99

10+
### Added
11+
12+
- X1OD: Added `IList<T>.Rotate(int)`.
13+
- X1OD: Added `Span<T>.Rotate(int)`.
14+
1015
### Changed
1116

1217
- X10D: Removed `IEnumerable<T>.GreatestCommonFactor` for all integer types in favour of generic math.

X10D.Tests/src/Collections/ListTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,46 @@ public void RemoveRange_ShouldRemoveElements_GivenList()
211211
Assert.That(list, Is.EqualTo(new[] { 1, 2, 7, 8, 9, 10 }).AsCollection);
212212
}
213213

214+
[Test]
215+
public void Rotate_ShouldShiftElements_ByNegativeShiftAmount()
216+
{
217+
int[] array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
218+
int[] expected = [5, 6, 7, 8, 9, 10, 1, 2, 3, 4];
219+
220+
array.Rotate(-4);
221+
222+
Assert.That(array, Is.EqualTo(expected).AsCollection);
223+
}
224+
225+
[Test]
226+
public void Rotate_ShouldShiftElements_ByPositiveShiftAmount()
227+
{
228+
int[] array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
229+
int[] expected = [7, 8, 9, 10, 1, 2, 3, 4, 5, 6];
230+
231+
array.Rotate(4);
232+
233+
Assert.That(array, Is.EqualTo(expected).AsCollection);
234+
}
235+
236+
[Test]
237+
public void Rotate_ShouldNotShiftElements_WithShift0()
238+
{
239+
int[] array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
240+
int[] expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
241+
242+
array.Rotate(0);
243+
244+
Assert.That(array, Is.EqualTo(expected).AsCollection);
245+
}
246+
247+
[Test]
248+
public void Rotate_ShouldThrowArgumentNullException_GivenNullSource()
249+
{
250+
int[] array = null!;
251+
Assert.Throws<ArgumentNullException>(() => array.Rotate(0));
252+
}
253+
214254
[Test]
215255
public void Shuffle_ShouldReorder_GivenNotNull()
216256
{

X10D.Tests/src/Collections/SpanTest.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using NUnit.Framework;
2+
using X10D.Collections;
23
#if !NET9_0_OR_GREATER
34
using X10D.Collections;
45
#endif
@@ -32,6 +33,39 @@ public void Replace_ShouldDoNothing_GivenSpanWithNoMatchingElements()
3233
Assert.That(span.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 2, 5, 2, 7, 2, 9, 2, 11, 2, 13, 2, 15, 2 }));
3334
}
3435

36+
[Test]
37+
public void Rotate_ShouldShiftElements_ByNegativeShiftAmount()
38+
{
39+
Span<int> array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
40+
Span<int> expected = [5, 6, 7, 8, 9, 10, 1, 2, 3, 4];
41+
42+
array.Rotate(-4);
43+
44+
Assert.That(array.ToArray(), Is.EqualTo(expected.ToArray()).AsCollection);
45+
}
46+
47+
[Test]
48+
public void Rotate_ShouldShiftElements_ByPositiveShiftAmount()
49+
{
50+
Span<int> array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
51+
Span<int> expected = [7, 8, 9, 10, 1, 2, 3, 4, 5, 6];
52+
53+
array.Rotate(4);
54+
55+
Assert.That(array.ToArray(), Is.EqualTo(expected.ToArray()).AsCollection);
56+
}
57+
58+
[Test]
59+
public void Rotate_ShouldNotShiftElements_WithShift0()
60+
{
61+
Span<int> array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
62+
Span<int> expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
63+
64+
array.Rotate(0);
65+
66+
Assert.That(array.ToArray(), Is.EqualTo(expected.ToArray()).AsCollection);
67+
}
68+
3569
#if !NET9_0_OR_GREATER
3670
[Test]
3771
public void Split_OnEmptySpan_ShouldYieldNothing_UsingCharDelimiter_GivenReadOnlySpan()

X10D/src/Collections/ListExtensions.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Diagnostics.Contracts;
22
using X10D.Core;
3+
using X10D.Math;
34

45
#pragma warning disable CA5394
56

@@ -249,6 +250,42 @@ public static void RemoveRange<T>(this IList<T> source, Range range)
249250
}
250251
}
251252

253+
/// <summary>
254+
/// Shifts the elements of the current list by a specified amount, wrapping them in the process.
255+
/// </summary>
256+
/// <param name="source">The list of elements to shift.</param>
257+
/// <param name="shift">The amount to shift.</param>
258+
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
259+
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
260+
public static void Rotate<T>(this IList<T> source, int shift)
261+
{
262+
if (source is null)
263+
{
264+
throw new ArgumentNullException(nameof(source));
265+
}
266+
267+
if (shift == 0)
268+
{
269+
return;
270+
}
271+
272+
shift = shift.Mod(source.Count);
273+
Reverse(source, 0, source.Count - 1);
274+
Reverse(source, 0, shift - 1);
275+
Reverse(source, shift, source.Count - 1);
276+
return;
277+
278+
static void Reverse(IList<T> list, int start, int end)
279+
{
280+
while (start < end)
281+
{
282+
(list[start], list[end]) = (list[end], list[start]);
283+
start++;
284+
end--;
285+
}
286+
}
287+
}
288+
252289
/// <summary>
253290
/// Reorganizes the elements in a list by implementing a Fisher-Yates shuffle.
254291
/// </summary>

X10D/src/Collections/SpanExtensions.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using X10D.Math;
2+
13
namespace X10D.Collections;
24

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

21+
/// <summary>
22+
/// Shifts the elements of the current span by a specified amount, wrapping them in the process.
23+
/// </summary>
24+
/// <param name="source">The span of elements to shift.</param>
25+
/// <param name="shift">The amount to shift.</param>
26+
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
27+
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
28+
public static void Rotate<T>(this Span<T> source, int shift)
29+
{
30+
if (shift == 0)
31+
{
32+
return;
33+
}
34+
35+
shift = shift.Mod(source.Length);
36+
Reverse(source, 0, source.Length - 1);
37+
Reverse(source, 0, shift - 1);
38+
Reverse(source, shift, source.Length - 1);
39+
return;
40+
41+
static void Reverse(Span<T> span, int start, int end)
42+
{
43+
while (start < end)
44+
{
45+
(span[start], span[end]) = (span[end], span[start]);
46+
start++;
47+
end--;
48+
}
49+
}
50+
}
51+
1952
#if !NET9_0_OR_GREATER
2053
/// <summary>
2154
/// Splits a span of elements into sub-spans based on a delimiting element.

0 commit comments

Comments
 (0)