diff --git a/Documentation/src/enumerable-extensions/cartesian-product.md b/Documentation/src/enumerable-extensions/cartesian-product.md
index 96b7528f..4d909744 100644
--- a/Documentation/src/enumerable-extensions/cartesian-product.md
+++ b/Documentation/src/enumerable-extensions/cartesian-product.md
@@ -1,38 +1,61 @@
## CartesianProduct
-In mathematics, specifically set theory, the Cartesian product of two sets A and B, denoted AΓB, is the set of all ordered pairs (a, b) where a is in A and b is in B.
+In mathematics, specifically set theory, the Cartesian product of two sets A and B, denoted AΓB,
+is the set of all ordered pairs (a, b) where a β A and b β B.
-The CartesianProduct extension function returns all possible pairs of two given IEnumerables.
-
-There are two overloads, one which lets you choose
+In other words: The Cartesian product produces all possible pairs of two given `IEnumerable`s.
![cartesian-product with marbles](cartesian-product.svg "Marble me!")
+### Recipe
+The Cartesian product can be easily implemented ad-hoc using LINQ's built-in `SelectMany` extension function:
+
+```cs
+using System;
+using System.Linq;
+
+// Version A: Get each pair as a tuple
+var result = sequenceA.SelectMany(_ => sequenceB, ValueTuple.Create);
+
+// Version B: Transform each pair using a selector
+var result = sequenceA.SelectMany(_ => sequenceB, (a, b) => ...);
+
+// Version C: Using LINQs declarative query syntax
+var result =
+ from a in sequenceA
+ from b in sequenceB
+ select ...;
+```
+
+
### Examples
Two sequences as input:
-```
+```
smiles = [π, π, π]
fruits = [π, π, π, π]
-```
+```
-The cartesian products of smiles and fruits:
+The Cartesian products of smiles and fruits:
-```
-smiles Γ fruits => [[π, π], [π, π], [π, π], [π, π],
- [π, π], [π, π], [π, π], [π, π],
+```
+smiles Γ fruits => [[π, π], [π, π], [π, π], [π, π],
+ [π, π], [π, π], [π, π], [π, π],
[π, π], [π, π], [π, π], [π, π]]
```
-In this C# example you see how all Playing cards are in fact a cartesian product of a suit and a value.
+In this C# example you see how all playing cards are in fact a Cartesian products of a suit and a value.
-This example uses the overload with our own selector, because we just want a sequence of strings.
+This example uses the overload with a selector, because we just want a sequence of strings.
```cs
-var suits = ImmutableList.Create("β ", "β£", "β₯", "β¦");
-var values =
- ImmutableList.Create("2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A");
+using System;
+using System.Linq;
+using Funcky;
+
+var suits = Sequence.Return("β ", "β£", "β₯", "β¦");
+var values = Sequence.Return("2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A");
-var allCards = suits.CartesianProduct(values, (suit, value) => $"{value}{suit}");
-```
+var deck = suits.SelectMany(_ => values, (suit, value) => $"{value}{suit}");
+```
diff --git a/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/ChunkTest.cs b/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/ChunkTest.cs
index dedef74a..72f85444 100644
--- a/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/ChunkTest.cs
+++ b/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/ChunkTest.cs
@@ -42,29 +42,29 @@ public async Task GivenAnEnumerableWeChanChunkItIntoAnEnumerableOfEnumerablesAsy
await AsyncAssert.Collection(
chunked,
- a =>
+ chunk =>
{
Assert.Collection(
- a,
- aa => Assert.Equal(1, aa),
- ab => Assert.Equal(2, ab),
- ac => Assert.Equal(3, ac));
+ chunk,
+ value => Assert.Equal(1, value),
+ value => Assert.Equal(2, value),
+ value => Assert.Equal(3, value));
},
- b =>
+ chunk =>
{
Assert.Collection(
- b,
- ba => Assert.Equal(4, ba),
- bb => Assert.Equal(5, bb),
- bc => Assert.Equal(6, bc));
+ chunk,
+ value => Assert.Equal(4, value),
+ value => Assert.Equal(5, value),
+ value => Assert.Equal(6, value));
},
- c =>
+ chunk =>
{
Assert.Collection(
- c,
- ca => Assert.Equal(7, ca),
- cb => Assert.Equal(8, cb),
- cc => Assert.Equal(9, cc));
+ chunk,
+ value => Assert.Equal(7, value),
+ value => Assert.Equal(8, value),
+ value => Assert.Equal(9, value));
});
}
@@ -79,18 +79,9 @@ public async Task GivenAnEnumerableNotAMultipleOfSizeWeHaveASmallerLastSlice()
await AsyncAssert.Collection(
chunked,
- a =>
- {
- Assert.Equal(chunkSize, a.Count);
- },
- b =>
- {
- Assert.Equal(chunkSize, b.Count);
- },
- c =>
- {
- Assert.Equal(count % chunkSize, c.Count);
- });
+ chunk => Assert.Equal(chunkSize, chunk.Count),
+ chunk => Assert.Equal(chunkSize, chunk.Count),
+ chunk => Assert.Equal(count % chunkSize, chunk.Count));
}
[Theory]
diff --git a/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/SlidingWindowTest.cs b/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/SlidingWindowTest.cs
index 4feee190..fe79ebb3 100644
--- a/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/SlidingWindowTest.cs
+++ b/Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/SlidingWindowTest.cs
@@ -41,7 +41,7 @@ public async Task GivenASourceSequenceShorterThanTheSlidingWindowWidthReturnsAnE
}
[Fact]
- public async Task SlidingWindowReturnsTheCorrectAmountOfWindowsAllOfEvenSizeAsync()
+ public async Task SlidingWindowReturnsTheCorrectAmountOfWindowsAllOfTheSameSizeAsync()
{
const int width = 5;
var source = AsyncEnumerable.Range(0, 10);
@@ -74,9 +74,9 @@ public async Task SlidingWindowReturnsASequenceOfConsecutiveWindowsAsync()
await AsyncAssert.Collection(
source.SlidingWindow(width),
- window1 => { Assert.Equal(Enumerable.Range(0, width), window1); },
- window2 => { Assert.Equal(Enumerable.Range(1, width), window2); },
- window3 => { Assert.Equal(Enumerable.Range(2, width), window3); });
+ window => Assert.Equal(Enumerable.Range(0, width), window),
+ window => Assert.Equal(Enumerable.Range(1, width), window),
+ window => Assert.Equal(Enumerable.Range(2, width), window));
}
[Fact]
diff --git a/Funcky.Async/Extensions/AsyncEnumerableExtensions/Chunk.cs b/Funcky.Async/Extensions/AsyncEnumerableExtensions/Chunk.cs
index c001000d..fcd2ceda 100644
--- a/Funcky.Async/Extensions/AsyncEnumerableExtensions/Chunk.cs
+++ b/Funcky.Async/Extensions/AsyncEnumerableExtensions/Chunk.cs
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
+using Funcky.Internal.Validators;
namespace Funcky.Extensions;
@@ -13,7 +14,7 @@ public static partial class AsyncEnumerableExtensions
/// A sequence of equally sized sequences containing elements of the source collection in the same order.
[Pure]
public static IAsyncEnumerable> Chunk(this IAsyncEnumerable source, int size)
- => ChunkEnumerable(source, ValidateChunkSize(size));
+ => ChunkEnumerable(source, ChunkSizeValidator.Validate(size));
///
/// Chunks the source sequence into equally sized chunks. The last chunk can be smaller.
@@ -26,7 +27,7 @@ public static IAsyncEnumerable> Chunk(this IAsyn
/// A sequence of results based on equally sized chunks.
[Pure]
public static IAsyncEnumerable Chunk(this IAsyncEnumerable source, int size, Func, TResult> resultSelector)
- => ChunkEnumerable(source, ValidateChunkSize(size))
+ => ChunkEnumerable(source, ChunkSizeValidator.Validate(size))
.Select(resultSelector);
///
@@ -40,7 +41,7 @@ public static IAsyncEnumerable Chunk(this IAsyncEnume
/// A sequence of results based on equally sized chunks.
[Pure]
public static IAsyncEnumerable ChunkAwait(this IAsyncEnumerable source, int size, Func, ValueTask> resultSelector)
- => ChunkEnumerable(source, ValidateChunkSize(size))
+ => ChunkEnumerable(source, ChunkSizeValidator.Validate(size))
.SelectAwait(resultSelector);
///
@@ -54,14 +55,9 @@ public static IAsyncEnumerable ChunkAwait(this IAsync
/// A sequence of results based on equally sized chunks.
[Pure]
public static IAsyncEnumerable ChunkAwaitWithCancellation(this IAsyncEnumerable source, int size, Func, CancellationToken, ValueTask> resultSelector)
- => ChunkEnumerable(source, ValidateChunkSize(size))
+ => ChunkEnumerable(source, ChunkSizeValidator.Validate(size))
.SelectAwaitWithCancellation(resultSelector);
- private static int ValidateChunkSize(int size)
- => size > 0
- ? size
- : throw new ArgumentOutOfRangeException(nameof(size), size, "Size must be bigger than 0");
-
private static async IAsyncEnumerable> ChunkEnumerable(IAsyncEnumerable source, int size, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var asyncEnumerator = source.GetAsyncEnumerator(cancellationToken);
diff --git a/Funcky.Async/Extensions/AsyncEnumerableExtensions/SlidingWindow.cs b/Funcky.Async/Extensions/AsyncEnumerableExtensions/SlidingWindow.cs
index 1f62d305..2a4e7989 100644
--- a/Funcky.Async/Extensions/AsyncEnumerableExtensions/SlidingWindow.cs
+++ b/Funcky.Async/Extensions/AsyncEnumerableExtensions/SlidingWindow.cs
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using Funcky.Internal;
+using Funcky.Internal.Validators;
namespace Funcky.Extensions;
@@ -19,7 +20,7 @@ public static partial class AsyncEnumerableExtensions
/// Returns a sequence of equally sized window sequences.
[Pure]
public static IAsyncEnumerable> SlidingWindow(this IAsyncEnumerable source, int width)
- => SlidingWindowEnumerable(source, ValidateWindowWidth(width));
+ => SlidingWindowEnumerable(source, WindowWidthValidator.Validate(width));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static async IAsyncEnumerable> SlidingWindowEnumerable(
@@ -36,10 +37,4 @@ private static async IAsyncEnumerable> SlidingWindowEnume
}
}
}
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int ValidateWindowWidth(int width)
- => width > 0
- ? width
- : throw new ArgumentOutOfRangeException(nameof(width), width, "The width of the window must be bigger than 0");
}
diff --git a/Funcky.Async/Funcky.Async.csproj b/Funcky.Async/Funcky.Async.csproj
index f508cd7a..da8da973 100644
--- a/Funcky.Async/Funcky.Async.csproj
+++ b/Funcky.Async/Funcky.Async.csproj
@@ -35,6 +35,8 @@
+
+
diff --git a/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs
index 7f3ffd2b..90f273cc 100644
--- a/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs
+++ b/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs
@@ -31,11 +31,11 @@ public void GivenAnSingleElementListWeGetEnumerableWithOneElement()
Assert.Collection(
chunked,
- a =>
+ chunk =>
{
Assert.Collection(
- a,
- aa => Assert.Equal(1, aa));
+ chunk,
+ value => Assert.Equal(1, value));
});
}
@@ -48,29 +48,29 @@ public void GivenAnEnumerableWeChanChunkItIntoAnEnumerableOfEnumerables()
Assert.Collection(
chunked,
- a =>
+ chunk =>
{
Assert.Collection(
- a,
- aa => Assert.Equal(1, aa),
- ab => Assert.Equal(2, ab),
- ac => Assert.Equal(3, ac));
+ chunk,
+ value => Assert.Equal(1, value),
+ value => Assert.Equal(2, value),
+ value => Assert.Equal(3, value));
},
- b =>
+ chunk =>
{
Assert.Collection(
- b,
- ba => Assert.Equal(4, ba),
- bb => Assert.Equal(5, bb),
- bc => Assert.Equal(6, bc));
+ chunk,
+ value => Assert.Equal(4, value),
+ value => Assert.Equal(5, value),
+ value => Assert.Equal(6, value));
},
- c =>
+ chunk =>
{
Assert.Collection(
- c,
- ca => Assert.Equal(7, ca),
- cb => Assert.Equal(8, cb),
- cc => Assert.Equal(9, cc));
+ chunk,
+ value => Assert.Equal(7, value),
+ value => Assert.Equal(8, value),
+ value => Assert.Equal(9, value));
});
}
@@ -84,18 +84,9 @@ public void GivenAnEnumerableNotAMultipleOfSizeWeHaveASmallerLastSlice()
Assert.Collection(
chunked,
- a =>
- {
- Assert.Equal(chunkSize, a.Count);
- },
- b =>
- {
- Assert.Equal(chunkSize, b.Count);
- },
- c =>
- {
- Assert.Equal(numbers.Count % chunkSize, c.Count);
- });
+ chunk => Assert.Equal(chunkSize, chunk.Count),
+ chunk => Assert.Equal(chunkSize, chunk.Count),
+ chunk => Assert.Equal(numbers.Count % chunkSize, chunk.Count));
}
[Theory]
diff --git a/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs
index d5aedda0..6d528635 100644
--- a/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs
+++ b/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs
@@ -40,7 +40,7 @@ public void GivenASourceSequenceShorterThanTheSlidingWindowWidthReturnsAnEmptySe
}
[Fact]
- public void SlidingWindowReturnsTheCorrectAmountOfWindowsAllOfEvenSize()
+ public void SlidingWindowReturnsTheCorrectAmountOfWindowsAllOfTheSameSize()
{
const int width = 5;
var source = Enumerable.Range(0, 10);
@@ -70,8 +70,8 @@ public void SlidingWindowReturnsASequenceOfConsecutiveWindows()
Assert.Collection(
source.SlidingWindow(width),
- window1 => { Assert.Equal(Enumerable.Range(0, width), window1); },
- window2 => { Assert.Equal(Enumerable.Range(1, width), window2); },
- window3 => { Assert.Equal(Enumerable.Range(2, width), window3); });
+ window => Assert.Equal(Enumerable.Range(0, width), window),
+ window => Assert.Equal(Enumerable.Range(1, width), window),
+ window => Assert.Equal(Enumerable.Range(2, width), window));
}
}
diff --git a/Funcky.Test/Extensions/StringExtensions/ChunkOnStringTest.cs b/Funcky.Test/Extensions/StringExtensions/ChunkOnStringTest.cs
new file mode 100644
index 00000000..e17531ac
--- /dev/null
+++ b/Funcky.Test/Extensions/StringExtensions/ChunkOnStringTest.cs
@@ -0,0 +1,54 @@
+using FsCheck;
+using FsCheck.Xunit;
+
+namespace Funcky.Test.Extensions.StringExtensions;
+
+public class ChunkOnStringTest
+{
+ [Property]
+ public Property ChunkingAnEmptyStringWillAlwaysYieldAndEmptySequence(PositiveInt width)
+ => string.Empty
+ .Chunk(width.Get)
+ .None()
+ .ToProperty();
+
+ [Fact]
+ public void GivenAnSingleElementListWeGetEnumerableWithOneElement()
+ {
+ var value = "a";
+
+ var chunked = value.Chunk(3);
+
+ Assert.Collection(
+ chunked,
+ chunk => Assert.Equal("a", chunk));
+ }
+
+ [Fact]
+ public void GivenAnStringWhichIsNotAMultipleOfSizeWeHaveASmallerLastString()
+ {
+ var value = "abcdefghij";
+
+ const int chunkSize = 4;
+ var chunked = value.Chunk(chunkSize);
+
+ Assert.Collection(
+ chunked,
+ chunk => Assert.Equal(chunkSize, chunk.Length),
+ chunk => Assert.Equal(chunkSize, chunk.Length),
+ chunk => Assert.Equal(value.Length % chunkSize, chunk.Length));
+ }
+
+ [Fact]
+ public void AChunkOfAStringReturnsAListOfConsecutivePartialStrings()
+ {
+ const int width = 3;
+ var source = "epsilon";
+
+ Assert.Collection(
+ source.Chunk(width),
+ chunk => { Assert.Equal("eps", chunk); },
+ chunk => { Assert.Equal("ilo", chunk); },
+ chunk => { Assert.Equal("n", chunk); });
+ }
+}
diff --git a/Funcky.Test/Extensions/StringExtensions/SlidingWindowOnStringTest.cs b/Funcky.Test/Extensions/StringExtensions/SlidingWindowOnStringTest.cs
new file mode 100644
index 00000000..36bbbd70
--- /dev/null
+++ b/Funcky.Test/Extensions/StringExtensions/SlidingWindowOnStringTest.cs
@@ -0,0 +1,57 @@
+using FsCheck;
+using FsCheck.Xunit;
+
+namespace Funcky.Test.Extensions.StringExtensions;
+
+public class SlidingWindowOnStringTest
+{
+ [Property]
+ public Property ASlidingWindowFromAnEmptyStringWillAlwaysYieldAnEmptySequence(PositiveInt width)
+ => string.Empty
+ .SlidingWindow(width.Get)
+ .None()
+ .ToProperty();
+
+ [Fact]
+ public void SlidingWindowReturnsAListOfOverlappingPartialStrings()
+ {
+ const int width = 4;
+ var source = "epsilon";
+
+ Assert.Collection(
+ source.SlidingWindow(width),
+ window => Assert.Equal("epsi", window),
+ window => Assert.Equal("psil", window),
+ window => Assert.Equal("silo", window),
+ window => Assert.Equal("ilon", window));
+ }
+
+ [Fact]
+ public void GivenASourceStringEqualInLengthToTheSlidingWindowWidthReturnsASequenceWithOneElement()
+ {
+ var source = "gamma";
+
+ Assert.Single(source.SlidingWindow(5));
+ Assert.Equal(source, source.SlidingWindow(5).Single());
+ }
+
+ [Fact]
+ public void GivenASourceSequenceShorterThanTheSlidingWindowWidthReturnsAnEmptySequence()
+ {
+ var source = "gamma";
+
+ Assert.Empty(source.SlidingWindow(10));
+ }
+
+ [Theory]
+ [InlineData(int.MinValue)]
+ [InlineData(-42)]
+ [InlineData(-1)]
+ [InlineData(0)]
+ public void SlidingWindowThrowsOnNonPositiveWidth(int width)
+ {
+ var source = "Just a simple test!";
+
+ Assert.Throws(() => source.SlidingWindow(width));
+ }
+}
diff --git a/Funcky/Extensions/EnumerableExtensions/Chunk.cs b/Funcky/Extensions/EnumerableExtensions/Chunk.cs
index fcbd0c5e..85eac260 100644
--- a/Funcky/Extensions/EnumerableExtensions/Chunk.cs
+++ b/Funcky/Extensions/EnumerableExtensions/Chunk.cs
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
+using Funcky.Internal.Validators;
namespace Funcky.Extensions;
@@ -18,7 +19,7 @@ public static IEnumerable> Chunk(IEnumerable> Chunk(this IEnumerable source, int size)
#endif
- => ChunkEnumerable(source, ValidateChunkSize(size));
+ => ChunkEnumerable(source, ChunkSizeValidator.Validate(size));
///
/// Chunks the source sequence into equally sized chunks. The last chunk can be smaller.
@@ -31,14 +32,9 @@ public static IEnumerable> Chunk(this IEnumerabl
/// A sequence of results based on equally sized chunks.
[Pure]
public static IEnumerable Chunk(this IEnumerable source, int size, Func, TResult> resultSelector)
- => ChunkEnumerable(source, ValidateChunkSize(size))
+ => ChunkEnumerable(source, ChunkSizeValidator.Validate(size))
.Select(resultSelector);
- private static int ValidateChunkSize(int size)
- => size > 0
- ? size
- : throw new ArgumentOutOfRangeException(nameof(size), size, "Size must be bigger than 0");
-
private static IEnumerable> ChunkEnumerable(IEnumerable source, int size)
{
using var sourceEnumerator = source.GetEnumerator();
diff --git a/Funcky/Extensions/EnumerableExtensions/SlidingWindow.cs b/Funcky/Extensions/EnumerableExtensions/SlidingWindow.cs
index 2cc44c45..8140a966 100644
--- a/Funcky/Extensions/EnumerableExtensions/SlidingWindow.cs
+++ b/Funcky/Extensions/EnumerableExtensions/SlidingWindow.cs
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using Funcky.Internal;
+using Funcky.Internal.Validators;
namespace Funcky.Extensions;
@@ -19,7 +20,7 @@ public static partial class EnumerableExtensions
/// Returns a sequence of equally sized window sequences.
[Pure]
public static IEnumerable> SlidingWindow(this IEnumerable source, int width)
- => SlidingWindowEnumerable(source, ValidateWindowWidth(width));
+ => SlidingWindowEnumerable(source, WindowWidthValidator.Validate(width));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IEnumerable> SlidingWindowEnumerable(IEnumerable source, int width)
@@ -33,10 +34,4 @@ private static IEnumerable> SlidingWindowEnumerable width > 0
- ? width
- : throw new ArgumentOutOfRangeException(nameof(width), width, "The width of the window must be larger than 0");
}
diff --git a/Funcky/Extensions/StringExtensions/Chunk.cs b/Funcky/Extensions/StringExtensions/Chunk.cs
new file mode 100644
index 00000000..f3404f61
--- /dev/null
+++ b/Funcky/Extensions/StringExtensions/Chunk.cs
@@ -0,0 +1,41 @@
+using System.Runtime.CompilerServices;
+using Funcky.Internal.Validators;
+
+namespace Funcky.Extensions;
+
+public static partial class StringExtensions
+{
+ private const int LastElement = 1;
+
+ ///
+ /// Chunks the source string into equally sized chunks. The last chunk can be smaller.
+ ///
+ /// The source string.
+ /// The desired size of the chunks in characters.
+ /// A sequence of equally sized substrings containing consecutive substrings of the source string in the same order.
+ public static IEnumerable Chunk(this string source, int size)
+ => ChunkString(source, ChunkSizeValidator.Validate(size));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static IEnumerable ChunkString(string source, int size)
+ {
+ var count = DivisionWithCeiling(source.Length, size);
+
+ // optimization: we do not emit the last chunk, because it might be smaller than size.
+ var index = 0;
+ for (; index < count - LastElement; ++index)
+ {
+ yield return source.Substring(index * size, size);
+ }
+
+ // If there is anything left to emit, we will emit the last chunk.
+ if (source.Length > 0)
+ {
+ yield return source.Substring(index * size);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int DivisionWithCeiling(int dividend, int divisior)
+ => (dividend + divisior - 1) / divisior;
+}
diff --git a/Funcky/Extensions/StringExtensions/SlidingWindow.cs b/Funcky/Extensions/StringExtensions/SlidingWindow.cs
new file mode 100644
index 00000000..0c0730c6
--- /dev/null
+++ b/Funcky/Extensions/StringExtensions/SlidingWindow.cs
@@ -0,0 +1,28 @@
+using System.Runtime.CompilerServices;
+using Funcky.Internal.Validators;
+
+namespace Funcky.Extensions;
+
+public static partial class StringExtensions
+{
+ ///
+ /// SlidingWindow returns a sequence of sliding window strings of the given width.
+ /// The nth string will start with the nth character of the source sequence.
+ ///
+ /// The source string.
+ /// The width of the sliding window in characters.
+ /// Returns a sequence of equally sized substrings.
+ public static IEnumerable SlidingWindow(this string source, int width)
+ => StringSlidingWindow(source, WindowWidthValidator.Validate(width));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static IEnumerable StringSlidingWindow(string source, int width)
+ {
+ var numberOfWindows = source.Length - width + 1;
+
+ for (var index = 0; index < numberOfWindows; ++index)
+ {
+ yield return source.Substring(index, width);
+ }
+ }
+}
diff --git a/Funcky/Internal/Validators/ChunkSizeValidator.cs b/Funcky/Internal/Validators/ChunkSizeValidator.cs
new file mode 100644
index 00000000..c3ce35da
--- /dev/null
+++ b/Funcky/Internal/Validators/ChunkSizeValidator.cs
@@ -0,0 +1,12 @@
+ο»Ώusing System.Runtime.CompilerServices;
+
+namespace Funcky.Internal.Validators;
+
+internal static class ChunkSizeValidator
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Validate(int size)
+ => size > 0
+ ? size
+ : throw new ArgumentOutOfRangeException(nameof(size), size, "Size must be bigger than 0");
+}
diff --git a/Funcky/Internal/Validators/WindowWidthValidator.cs b/Funcky/Internal/Validators/WindowWidthValidator.cs
new file mode 100644
index 00000000..94885e4a
--- /dev/null
+++ b/Funcky/Internal/Validators/WindowWidthValidator.cs
@@ -0,0 +1,12 @@
+using System.Runtime.CompilerServices;
+
+namespace Funcky.Internal.Validators;
+
+internal static class WindowWidthValidator
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Validate(int width)
+ => width > 0
+ ? width
+ : throw new ArgumentOutOfRangeException(nameof(width), width, "The width of the window must be larger than 0");
+}
diff --git a/Funcky/PublicAPI.Unshipped.txt b/Funcky/PublicAPI.Unshipped.txt
index 00f48e94..5a25287e 100644
--- a/Funcky/PublicAPI.Unshipped.txt
+++ b/Funcky/PublicAPI.Unshipped.txt
@@ -14,3 +14,5 @@ static Funcky.Extensions.ParseExtensions.ParseNumberOrNone(this string!
static Funcky.Extensions.ParseExtensions.ParseNumberOrNone(this System.ReadOnlySpan value, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option
static Funcky.Extensions.ParseExtensions.ParseOrNone(this string? value, System.IFormatProvider? provider) -> Funcky.Monads.Option
static Funcky.Extensions.ParseExtensions.ParseOrNone(this System.ReadOnlySpan value, System.IFormatProvider? provider) -> Funcky.Monads.Option
+static Funcky.Extensions.StringExtensions.Chunk(this string! source, int size) -> System.Collections.Generic.IEnumerable!
+static Funcky.Extensions.StringExtensions.SlidingWindow(this string! source, int width) -> System.Collections.Generic.IEnumerable!