From 59caf206eabf69f55f33662d4de283b00aafe772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Tue, 14 Jan 2025 10:32:57 +0100 Subject: [PATCH 1/3] Add {Cycle,Repeat}Materialized --- Funcky.Test/Sequence/CycleMaterializedTest.cs | 47 +++++++++++++++++++ Funcky.Test/Sequence/CycleRangeTest.cs | 4 +- .../Sequence/RepeatMaterializedTest.cs | 43 +++++++++++++++++ .../TestUtils/FailOnEnumerateCollection.cs | 2 +- Funcky/PublicAPI.Unshipped.txt | 2 + Funcky/Sequence/Sequence.CycleMaterialized.cs | 11 +++++ .../Sequence/Sequence.RepeatMaterialized.cs | 9 ++++ 7 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 Funcky.Test/Sequence/CycleMaterializedTest.cs create mode 100644 Funcky.Test/Sequence/RepeatMaterializedTest.cs create mode 100644 Funcky/Sequence/Sequence.CycleMaterialized.cs create mode 100644 Funcky/Sequence/Sequence.RepeatMaterialized.cs diff --git a/Funcky.Test/Sequence/CycleMaterializedTest.cs b/Funcky.Test/Sequence/CycleMaterializedTest.cs new file mode 100644 index 00000000..4de17480 --- /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 FailOnEnumerateCollection(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..b3d06317 --- /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 FailOnEnumerateCollection(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/FailOnEnumerateCollection.cs b/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs index 84c1eba0..34652a78 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs +++ b/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs @@ -3,7 +3,7 @@ namespace Funcky.Test.TestUtils; -internal record FailOnEnumerateCollection(int Count) : ICollection +internal record FailOnEnumerateCollection(int Count) : ICollection, IReadOnlyCollection { public bool IsReadOnly => true; 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..c18b2713 --- /dev/null +++ b/Funcky/Sequence/Sequence.CycleMaterialized.cs @@ -0,0 +1,11 @@ +namespace Funcky; + +public static partial class 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.RepeatMaterialized.cs b/Funcky/Sequence/Sequence.RepeatMaterialized.cs new file mode 100644 index 00000000..94c0a2bf --- /dev/null +++ b/Funcky/Sequence/Sequence.RepeatMaterialized.cs @@ -0,0 +1,9 @@ +namespace Funcky; + +public static partial class Sequence +{ + /// + [Pure] + public static IEnumerable RepeatMaterialized(IReadOnlyCollection source, int count) + => Enumerable.Repeat(source, count).SelectMany(Identity); +} From 1da0f3ed2bbd283b0dbbbd30bf4152b4125ade2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Tue, 14 Jan 2025 10:41:03 +0100 Subject: [PATCH 2/3] Create separate read only fail on enumerate collection --- Funcky.Test/Sequence/CycleMaterializedTest.cs | 2 +- Funcky.Test/Sequence/RepeatMaterializedTest.cs | 2 +- Funcky.Test/TestUtils/FailOnEnumerateCollection.cs | 2 +- .../TestUtils/FailOnEnumerateReadOnlyCollection.cs | 11 +++++++++++ 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs diff --git a/Funcky.Test/Sequence/CycleMaterializedTest.cs b/Funcky.Test/Sequence/CycleMaterializedTest.cs index 4de17480..64fce810 100644 --- a/Funcky.Test/Sequence/CycleMaterializedTest.cs +++ b/Funcky.Test/Sequence/CycleMaterializedTest.cs @@ -9,7 +9,7 @@ public sealed class CycleMaterializedTest [Fact] public void IsEnumeratedLazily() { - var doNotEnumerate = new FailOnEnumerateCollection(Count: 1); + var doNotEnumerate = new FailOnEnumerateReadOnlyCollection(Count: 1); _ = Sequence.CycleMaterialized(doNotEnumerate); } diff --git a/Funcky.Test/Sequence/RepeatMaterializedTest.cs b/Funcky.Test/Sequence/RepeatMaterializedTest.cs index b3d06317..449b2267 100644 --- a/Funcky.Test/Sequence/RepeatMaterializedTest.cs +++ b/Funcky.Test/Sequence/RepeatMaterializedTest.cs @@ -9,7 +9,7 @@ public sealed class RepeatMaterializedTest [Fact] public void IsEnumeratedLazily() { - var doNotEnumerate = new FailOnEnumerateCollection(Count: 0); + var doNotEnumerate = new FailOnEnumerateReadOnlyCollection(Count: 0); _ = Sequence.RepeatMaterialized(doNotEnumerate, 2); } diff --git a/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs b/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs index 34652a78..84c1eba0 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs +++ b/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs @@ -3,7 +3,7 @@ namespace Funcky.Test.TestUtils; -internal record FailOnEnumerateCollection(int Count) : ICollection, IReadOnlyCollection +internal record FailOnEnumerateCollection(int Count) : ICollection { public bool IsReadOnly => true; 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(); +} From ce7a40895bfe96bce7159aca4c36d562191a674c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Tue, 14 Jan 2025 10:47:04 +0100 Subject: [PATCH 3/3] Hint at other version in docs --- Funcky/Sequence/Sequence.CycleMaterialized.cs | 8 +++++++- Funcky/Sequence/Sequence.CycleRange.cs | 1 + Funcky/Sequence/Sequence.RepeatMaterialized.cs | 9 ++++++++- Funcky/Sequence/Sequence.RepeatRange.cs | 1 + 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Funcky/Sequence/Sequence.CycleMaterialized.cs b/Funcky/Sequence/Sequence.CycleMaterialized.cs index c18b2713..691b9c55 100644 --- a/Funcky/Sequence/Sequence.CycleMaterialized.cs +++ b/Funcky/Sequence/Sequence.CycleMaterialized.cs @@ -2,7 +2,13 @@ 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 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 index 94c0a2bf..c15d1166 100644 --- a/Funcky/Sequence/Sequence.RepeatMaterialized.cs +++ b/Funcky/Sequence/Sequence.RepeatMaterialized.cs @@ -2,7 +2,14 @@ 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);