Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 Add simpler Unfold overload #1015

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 60 additions & 32 deletions MoreLinq.Test/UnfoldTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,80 +17,108 @@

namespace MoreLinq.Test
{
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using NUnit.Framework;

[TestFixture]
public class UnfoldTest
{
[Test]
public void UnfoldInfiniteSequence()
{
var result = MoreEnumerable.Unfold(1, x => (Result: x, State: x + 1),
static IEnumerable<TestCaseData>
TestCaseData<T>(IEnumerable<T> source1,
IEnumerable<T> source2,
[CallerMemberName] string? callerMemberName = null) =>
[
new(source1) { TestName = $"{callerMemberName}(generator)" },
new(source2) { TestName = $"{callerMemberName}(state, generator, predicate, stateSelector, resultSelector)" },
];

static IEnumerable<TestCaseData> UnfoldInfiniteSequenceData() =>
TestCaseData(MoreEnumerable.Unfold(1, x => (true, (Result: x, State: x + 1))),
MoreEnumerable.Unfold(1, x => (Result: x, State: x + 1),
_ => true,
e => e.State,
e => e.Result)
.Take(100);
e => e.Result));

[Test]
[TestCaseSource(nameof(UnfoldInfiniteSequenceData))]
public void UnfoldInfiniteSequence(IEnumerable<int> unfold)
{
var result = unfold.Take(100);
var expectations = MoreEnumerable.Generate(1, x => x + 1).Take(100);

Assert.That(result, Is.EqualTo(expectations));
}

[Test]
public void UnfoldFiniteSequence()
{
var result = MoreEnumerable.Unfold(1, x => (Result: x, State: x + 1),
static IEnumerable<TestCaseData> UnfoldFiniteSequenceData() =>
TestCaseData(MoreEnumerable.Unfold(1, x => x <= 100 ? (true, (Result: x, State: x + 1)) : default),
MoreEnumerable.Unfold(1, x => (Result: x, State: x + 1),
e => e.Result <= 100,
e => e.State,
e => e.Result);
e => e.Result));

[Test]
[TestCaseSource(nameof(UnfoldFiniteSequenceData))]
public void UnfoldFiniteSequence(IEnumerable<int> unfold)
{
var result = unfold;
var expectations = MoreEnumerable.Generate(1, x => x + 1).Take(100);

Assert.That(result, Is.EqualTo(expectations));
}

[Test]
public void UnfoldIsLazy()
{
_ = MoreEnumerable.Unfold(0, BreakingFunc.Of<int, (bool, (int, int))>());

_ = MoreEnumerable.Unfold(0, BreakingFunc.Of<int, (int, int)>(),
BreakingFunc.Of<(int, int), bool>(),
BreakingFunc.Of<(int, int), int>(),
BreakingFunc.Of<(int, int), int>());
}


[Test]
public void UnfoldSingleElementSequence()
{
var result = MoreEnumerable.Unfold(0, x => (Result: x, State: x + 1),
static IEnumerable<TestCaseData> UnfoldSingleElementSequenceData() =>
TestCaseData(MoreEnumerable.Unfold(0, x => x == 0 ? (true, (Result: x, State: x + 1)) : default),
MoreEnumerable.Unfold(0, x => (Result: x, State: x + 1),
x => x.Result == 0,
e => e.State,
e => e.Result);
e => e.Result));

[Test]
[TestCaseSource(nameof(UnfoldSingleElementSequenceData))]
public void UnfoldSingleElementSequence(IEnumerable<int> unfold)
{
var result = unfold;
var expectations = new[] { 0 };

Assert.That(result, Is.EqualTo(expectations));
}

[Test]
public void UnfoldEmptySequence()
{
var result = MoreEnumerable.Unfold(0, x => (Result: x, State: x + 1),
static IEnumerable<TestCaseData> UnfoldEmptySequenceData() =>
TestCaseData(MoreEnumerable.Unfold(0, x => x < 0 ? (true, (Result: x, State: x + 1)) : default),
MoreEnumerable.Unfold(0, x => (Result: x, State: x + 1),
x => x.Result < 0,
e => e.State,
e => e.Result);
e => e.Result));

[Test]
[TestCaseSource(nameof(UnfoldEmptySequenceData))]
public void UnfoldEmptySequence(IEnumerable<int> unfold)
{
var result = unfold;
Assert.That(result, Is.Empty);
}

static IEnumerable<TestCaseData> UnfoldReiterationsReturnsSameResultData() =>
TestCaseData(MoreEnumerable.Unfold(1, n => (true, (Result: n, State: n + 1))),
MoreEnumerable.Unfold(1, n => (Result: n, Next: n + 1),
_ => true,
n => n.Next,
n => n.Result));

[Test(Description = "https://github.com/morelinq/MoreLINQ/issues/990")]
public void UnfoldReiterationsReturnsSameResult()
[TestCaseSource(nameof(UnfoldReiterationsReturnsSameResultData))]
public void UnfoldReiterationsReturnsSameResult(IEnumerable<int> unfold)
{
var xs = MoreEnumerable.Unfold(1, n => (Result: n, Next: n + 1),
_ => true,
n => n.Next,
n => n.Result)
.Take(5);

var xs = unfold.Take(5);
xs.AssertSequenceEqual(1, 2, 3, 4, 5);
xs.AssertSequenceEqual(1, 2, 3, 4, 5);
}
Expand Down
1 change: 1 addition & 0 deletions MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
static MoreLinq.MoreEnumerable.Unfold<TState, TResult>(TState state, System.Func<TState, (bool Continue, (TResult Result, TState State) Generated)>! generator) -> System.Collections.Generic.IEnumerable<TResult>!
1 change: 1 addition & 0 deletions MoreLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
static MoreLinq.MoreEnumerable.Unfold<TState, TResult>(TState state, System.Func<TState, (bool Continue, (TResult Result, TState State) Generated)>! generator) -> System.Collections.Generic.IEnumerable<TResult>!
1 change: 1 addition & 0 deletions MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
static MoreLinq.MoreEnumerable.Unfold<TState, TResult>(TState state, System.Func<TState, (bool Continue, (TResult Result, TState State) Generated)>! generator) -> System.Collections.Generic.IEnumerable<TResult>!
1 change: 1 addition & 0 deletions MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
static MoreLinq.MoreEnumerable.Unfold<TState, TResult>(TState state, System.Func<TState, (bool Continue, (TResult Result, TState State) Generated)>! generator) -> System.Collections.Generic.IEnumerable<TResult>!
38 changes: 38 additions & 0 deletions MoreLinq/Unfold.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,44 @@ namespace MoreLinq

static partial class MoreEnumerable
{
/// <summary>
/// Returns a sequence generated by applying a state to the generator function, which
/// optionally determines the next state and element of the sequence.
/// </summary>
/// <typeparam name="TState">Type of state elements.</typeparam>
/// <typeparam name="TResult">The type of the elements of the result sequence.</typeparam>
/// <param name="state">The initial state.</param>
/// <param name="generator">
/// Function that takes a state and computes a Boolean coupled with the element of the
/// sequence to yield and the next state. The Boolean value indicates whether the sequence
/// should continue or not. If <c>false</c>, the sequence ends and the remaining bits, like
/// element and next state are ignored.
/// </param>
/// <returns>
/// A sequence containing the results generated by the <paramref name="generator"/>
/// function.</returns>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// </remarks>
/// <example>
/// <code><![CDATA[
/// var fibonacciNumbersLowerThan100 =
/// MoreEnumerable.Unfold((Curr: 0, Next: 1),
/// s => s.Curr < 100 ? (true, s.Curr, (s.Next, s.Curr + s.Next)) : default);
/// ]]></code>
/// The <c>fibonacciNumbersLowerThan100</c> will be a sequence that will yield
/// <c>{ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }</c>.
/// </example>

public static IEnumerable<TResult> Unfold<TState, TResult>(
TState state,
Func<TState, (bool Continue, (TResult Result, TState State) Generated)> generator)
{
if (generator == null) throw new ArgumentNullException(nameof(generator));

return Unfold(state, generator, e => e.Continue, e => e.Generated.State, e => e.Generated.Result);
}

/// <summary>
/// Returns a sequence generated by applying a state to the generator function,
/// and from its result, determines if the sequence should have a next element, its value,
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,8 @@ Returns a sequence generated by applying a state to the generator function,
and from its result, determines if the sequence should have a next element and
its value, and the next state in the recursive call.

This method has 2 overloads.

### Window

Processes a sequence into a series of subsequences representing a windowed
Expand Down
Loading