diff --git a/Funcky.Test/Sequence/CycleMaterializedTest.cs b/Funcky.Test/Sequence/CycleMaterializedTest.cs new file mode 100644 index 00000000..64fce810 --- /dev/null +++ b/Funcky.Test/Sequence/CycleMaterializedTest.cs @@ -0,0 +1,47 @@ +using FsCheck; +using FsCheck.Xunit; +using Funcky.Test.TestUtils; + +namespace Funcky.Test; + +public sealed class CycleMaterializedTest +{ + [Fact] + public void IsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateReadOnlyCollection(Count: 1); + _ = Sequence.CycleMaterialized(doNotEnumerate); + } + + [Fact] + public void CyclingAnEmptySetThrowsAnException() + => Assert.Throws(CycleEmptySequence); + + [Property] + public Property CanProduceArbitraryManyItems(NonEmptySet sequence, PositiveInt arbitraryElements) + { + var cycleRange = Sequence.CycleMaterialized(sequence.Get.Materialize()); + + return (cycleRange.Take(arbitraryElements.Get).Count() == arbitraryElements.Get) + .ToProperty(); + } + + [Property] + public Property RepeatsTheElementsArbitraryManyTimes(NonEmptySet sequence, PositiveInt arbitraryElements) + { + var cycleRange = Sequence.CycleMaterialized(sequence.Get.Materialize()); + + return cycleRange + .IsSequenceRepeating(sequence.Get) + .NTimes(arbitraryElements.Get) + .ToProperty(); + } + + private static void CycleEmptySequence() + { + var cycledRange = Sequence.CycleMaterialized(Array.Empty()); + using var enumerator = cycledRange.GetEnumerator(); + + enumerator.MoveNext(); + } +} diff --git a/Funcky.Test/Sequence/CycleRangeTest.cs b/Funcky.Test/Sequence/CycleRangeTest.cs index e25a6018..9028ae9f 100644 --- a/Funcky.Test/Sequence/CycleRangeTest.cs +++ b/Funcky.Test/Sequence/CycleRangeTest.cs @@ -15,8 +15,8 @@ public void CycleRangeIsEnumeratedLazily() } [Fact] - public void CyclingAnEmptySetThrowsAnArgumentException() - => Assert.Throws(CycleEmptySequence); + public void CyclingAnEmptySetThrowsAnException() + => Assert.Throws(CycleEmptySequence); [Property] public Property CycleRangeCanProduceArbitraryManyItems(NonEmptySet sequence, PositiveInt arbitraryElements) diff --git a/Funcky.Test/Sequence/RepeatMaterializedTest.cs b/Funcky.Test/Sequence/RepeatMaterializedTest.cs new file mode 100644 index 00000000..449b2267 --- /dev/null +++ b/Funcky.Test/Sequence/RepeatMaterializedTest.cs @@ -0,0 +1,43 @@ +using FsCheck; +using FsCheck.Xunit; +using Funcky.Test.TestUtils; + +namespace Funcky.Test; + +public sealed class RepeatMaterializedTest +{ + [Fact] + public void IsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateReadOnlyCollection(Count: 0); + _ = Sequence.RepeatMaterialized(doNotEnumerate, 2); + } + + [Property] + public Property ARepeatedEmptySequenceIsStillEmpty(NonNegativeInt count) + { + var repeated = Sequence.RepeatMaterialized(Array.Empty(), count.Get); + return (!repeated.Any()).ToProperty(); + } + + [Property] + public Property TheLengthOfTheGeneratedSequenceIsCorrect(List list, NonNegativeInt count) + { + var repeatRange = Sequence.RepeatMaterialized(list, count.Get); + + var materialized = repeatRange.ToList(); + + return (materialized.Count == list.Count * count.Get).ToProperty(); + } + + [Property] + public Property TheSequenceRepeatsTheGivenNumberOfTimes(List list, NonNegativeInt count) + { + var repeatRange = Sequence.RepeatMaterialized(list, count.Get); + + return repeatRange + .IsSequenceRepeating(list) + .NTimes(count.Get) + .ToProperty(); + } +} diff --git a/Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs b/Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs new file mode 100644 index 00000000..a21fed57 --- /dev/null +++ b/Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs @@ -0,0 +1,11 @@ +using System.Collections; +using Xunit.Sdk; + +namespace Funcky.Test.TestUtils; + +internal record FailOnEnumerateReadOnlyCollection(int Count) : IReadOnlyCollection +{ + public IEnumerator GetEnumerator() => throw new XunitException("Should not be enumerated"); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/Funcky/PublicAPI.Unshipped.txt b/Funcky/PublicAPI.Unshipped.txt index df864c0e..e36f71a7 100644 --- a/Funcky/PublicAPI.Unshipped.txt +++ b/Funcky/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ static Funcky.DownCast.From(Funcky.Monads.Result result) static Funcky.DownCast.From(Funcky.Monads.Either either, System.Func! failedCast) -> Funcky.Monads.Either static Funcky.Extensions.ParseExtensions.ParseTypeNameOrNone(this System.ReadOnlySpan candidate, System.Reflection.Metadata.TypeNameParseOptions? options = null) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseAssemblyNameInfoOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Sequence.CycleMaterialized(System.Collections.Generic.IReadOnlyCollection! source) -> System.Collections.Generic.IEnumerable! +static Funcky.Sequence.RepeatMaterialized(System.Collections.Generic.IReadOnlyCollection! source, int count) -> System.Collections.Generic.IEnumerable! diff --git a/Funcky/Sequence/Sequence.CycleMaterialized.cs b/Funcky/Sequence/Sequence.CycleMaterialized.cs new file mode 100644 index 00000000..691b9c55 --- /dev/null +++ b/Funcky/Sequence/Sequence.CycleMaterialized.cs @@ -0,0 +1,17 @@ +namespace Funcky; + +public static partial class Sequence +{ + /// + /// Generates a sequence that contains the same sequence of elements over and over again as an endless generator. + /// + /// Type of the elements to be cycled. + /// The sequence of elements which are cycled. Throws an exception if the sequence is empty. + /// Returns an infinite IEnumerable repeating the same sequence of elements. + /// Use if you need to cycle a lazy sequence. + [Pure] + public static IEnumerable CycleMaterialized(IReadOnlyCollection source) + => source.Count > 0 + ? Cycle(source).SelectMany(Identity) + : throw new InvalidOperationException("you cannot cycle an empty enumerable"); +} diff --git a/Funcky/Sequence/Sequence.CycleRange.cs b/Funcky/Sequence/Sequence.CycleRange.cs index 912da0e4..ae2a48ae 100644 --- a/Funcky/Sequence/Sequence.CycleRange.cs +++ b/Funcky/Sequence/Sequence.CycleRange.cs @@ -11,6 +11,7 @@ public static partial class Sequence /// Type of the elements to be cycled. /// The sequence of elements which are cycled. Throws an exception if the sequence is empty. /// Returns an infinite IEnumerable repeating the same sequence of elements. + /// Use if you need to cycle an already materialized sequence. [Pure] public static IBuffer CycleRange(IEnumerable source) => CycleBuffer.Create(source); diff --git a/Funcky/Sequence/Sequence.RepeatMaterialized.cs b/Funcky/Sequence/Sequence.RepeatMaterialized.cs new file mode 100644 index 00000000..c15d1166 --- /dev/null +++ b/Funcky/Sequence/Sequence.RepeatMaterialized.cs @@ -0,0 +1,16 @@ +namespace Funcky; + +public static partial class Sequence +{ + /// + /// Generates a sequence that contains the same sequence of elements the given number of times. + /// + /// Type of the elements to be repeated. + /// The sequence of elements to be repeated. + /// The number of times to repeat the value in the generated sequence. + /// Returns an infinite IEnumerable cycling through the same elements. + /// Use if you need to cycle a lazy sequence. + [Pure] + public static IEnumerable RepeatMaterialized(IReadOnlyCollection source, int count) + => Enumerable.Repeat(source, count).SelectMany(Identity); +} diff --git a/Funcky/Sequence/Sequence.RepeatRange.cs b/Funcky/Sequence/Sequence.RepeatRange.cs index 0e0b4e21..531028a8 100644 --- a/Funcky/Sequence/Sequence.RepeatRange.cs +++ b/Funcky/Sequence/Sequence.RepeatRange.cs @@ -9,6 +9,7 @@ public static partial class Sequence /// The sequence of elements to be repeated. /// The number of times to repeat the value in the generated sequence. /// Returns an infinite IEnumerable cycling through the same elements. + /// Use if you need to cycle an already materialized sequence. [Pure] public static IBuffer RepeatRange(IEnumerable source, int count) => CycleBuffer.Create(source, count);