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

FillBackward: add tests, improve implementations. #693

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
173 changes: 145 additions & 28 deletions MoreLinq.Test/FillBackwardTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,165 @@
namespace MoreLinq.Test
{
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using System.Collections.Generic;

[TestFixture]
public class FillBackwardTest
{
[Test]
private static IEnumerable<T> Seq<T>(params T[] values) => values;

[TestCase(TestName = "FillBackward is lazy")]
public void FillBackwardIsLazy()
{
new BreakingSequence<object>().FillBackward();
var source = new BreakingSequence<object>();
source.FillBackward();
}

[Test]
public void FillBackward()
public static readonly IEnumerable<ITestCaseData> FillBackwardTestData =
from e in new[]
{
new
{
Name = "FillBackward: Input sequence is empty",
Source = Enumerable.Empty<int?>(),
Expected = Enumerable.Empty<int?>()
},
new
{
Name = "FillBackward: Input sequence has no null values",
Source = Seq<int?>(1, 2, 3),
Expected = Seq<int?>(1, 2, 3)
},
new
{
Name = "FillBackward: Input sequence has only nulls but last value",
Source = Seq<int?>(null, null, 0),
Expected = Seq<int?>(0, 0, 0)
},
new
{
Name = "FillBackward: Input sequence ends with nulls",
Source = Seq<int?>(null, null, 0, null, null),
Expected = Seq<int?>(0, 0, 0, null, null)
},
new
{
Name = "FillBackward: Input sequence with distributed nulls",
Source = Seq<int?>(0, null, null, 1, 2, null, null, 3, 4, null, null),
Expected = Seq<int?>(0, 1, 1, 1, 2, 3, 3, 3, 4, null, null)
}
}
select new TestCaseData(e.Source) {ExpectedResult = e.Expected, TestName = e.Name};

[TestCaseSource(nameof(FillBackwardTestData))]
public IEnumerable<int?> FillBackward(IEnumerable<int?> source)
{
int? na = null;
var input = new[] { na, na, 1, 2, na, na, na, 3, 4, na, na };
var result = input.FillBackward();
Assert.That(result, Is.EqualTo(new[] { 1, 1, 1, 2, 3, 3, 3, 3, 4, na, na }));
return source.AsTestingSequence().FillBackward();
}

[Test]
public void FillBackwardWithFillSelector()
[TestCase(TestName = "FillBackward with predicate is lazy")]
public void FillBackwardWithPredicateIsLazy()
{
var xs = new[] { 0, 0, 1, 2, 0, 0, 0, 3, 4, 0, 0 };

var result =
xs.Select(x => new { X = x, Y = x })
.FillBackward(e => e.X == 0, (m, nm) => new { m.X, nm.Y });
var source = new BreakingSequence<object>();
var predicate = BreakingFunc.Of<object, bool>();
source.FillBackward(predicate);
}

Assert.That(result, Is.EqualTo(new[]
public static readonly IEnumerable<ITestCaseData> FillBackwardWithPredicateTestData =
from e in new[]
{
new { X = 0, Y = 1 },
new { X = 0, Y = 1 },
new { X = 1, Y = 1 },
new { X = 2, Y = 2 },
new { X = 0, Y = 3 },
new { X = 0, Y = 3 },
new { X = 0, Y = 3 },
new { X = 3, Y = 3 },
new { X = 4, Y = 4 },
new { X = 0, Y = 0 },
new { X = 0, Y = 0 },
}));
new
{
Name = "FillBackward, with predicate: Input sequence is empty",
Source = Enumerable.Empty<int>(),
Expected = Enumerable.Empty<int>()
},
new
{
Name = "FillBackward, with predicate: Input sequence has no rejected values",
Source = Seq(1, 2, 3),
Expected = Seq(1, 2, 3)
},
new
{
Name = "FillBackward, with predicate: Input sequence has only rejected values but last value",
Source = Seq(-1, -1, 0),
Expected = Seq(0, 0, 0)
},
new
{
Name = "FillBackward, with predicate: Input sequence ends with rejected values",
Source = Seq(-1, -1, 0, -1, -1),
Expected = Seq(0, 0, 0, -1, -1)
},
new
{
Name = "FillBackward, with predicate: Input sequence with distributed rejected values",
Source = Seq(0, -1, -1, 1, 2, -1, -1, 3, 4, -1, -1),
Expected = Seq(0, 1, 1, 1, 2, 3, 3, 3, 4, -1, -1)
}
}
select new TestCaseData(e.Source) {ExpectedResult = e.Expected, TestName = e.Name};

[TestCaseSource(nameof(FillBackwardWithPredicateTestData))]
public IEnumerable<int> FillBackwardWithPredicate(IEnumerable<int> source)
{
return source.AsTestingSequence().FillBackward(v => v < 0);
}

[TestCase(TestName = "FillBackward with predicate and fill selector is lazy")]
public void FillBackwardWithPredicateAndFillSelectorIsLazy()
{
var source = new BreakingSequence<object>();
var predicate = BreakingFunc.Of<object, bool>();
var fillSelector = BreakingFunc.Of<object, object, object>();
source.FillBackward(predicate, fillSelector);
}

public static readonly IEnumerable<ITestCaseData>
FillBackwardWithPredicateAndFillSelectorTestData =
from e in new[]
{
new
{
Name = "FillBackward, with predicate and fill selector: Input sequence is empty",
Source = Enumerable.Empty<int>(),
Expected = Enumerable.Empty<int>()
},
new
{
Name = "FillBackward, with predicate and fill selector: Input sequence has no rejected values",
Source = Seq(1, 2, 3),
Expected = Seq(1, 2, 3)
},
new
{
Name = "FillBackward, with predicate and fill selector: Input sequence has only rejected values but last value",
Source = Seq(-2, -1, 5),
Expected = Seq(25, 15, 5)
},
new
{
Name = "FillBackward, with predicate and fill selector: Input sequence ends with rejected values",
Source = Seq(-2, -1, 5, -1, -1),
Expected = Seq(25, 15, 5, -1, -1)
},
new
{
Name = "FillBackward, with predicate and fill selector: Input sequence with distributed rejected values",
Source = Seq(0, -2, -1, 1, 2, -2, -1, 3, 4, -1, -1),
Expected = Seq(0, 21, 11, 1, 2, 23, 13, 3, 4, -1, -1)
}
}
select new TestCaseData(e.Source) {ExpectedResult = e.Expected, TestName = e.Name};

[TestCaseSource(nameof(FillBackwardWithPredicateAndFillSelectorTestData))]
public IEnumerable<int> FillBackwardWithPredicateAndFillSelector(IEnumerable<int> source)
{
static bool Predicate(int v) => v < 0;
static int FillSelector(int missing, int nonMissing) => nonMissing - 10 * missing;
return source.AsTestingSequence().FillBackward(Predicate, FillSelector);
}
}
}
72 changes: 46 additions & 26 deletions MoreLinq/FillBackward.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,41 @@ public static IEnumerable<T> FillBackward<T>(this IEnumerable<T> source, Func<T,
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));

return FillBackwardImpl(source, predicate, null);
return _(); IEnumerable<T> _()
{
// We need to store the blanks in case of the source sequence ends with missing values
var blanks = new List<T>();

foreach (var item in source)
{
// Check if the item is ‘missing’
if (predicate(item))
{
blanks.Add(item);
}
else
{
if (blanks.Count > 0)
{
for (var i = 0; i < blanks.Count; i++)
yield return item;

blanks.Clear();
}

yield return item;
}
}

foreach (var blank in blanks)
yield return blank;
}
}

/// <summary>
/// Returns a sequence with each missing element in the source replaced
/// with the following non-missing element in that sequence. Additional
/// parameters specifiy two functions, one used to determine if an
/// parameters specify two functions, one used to determine if an
/// element is considered missing or not and another to provide the
/// replacement for the missing element.
/// </summary>
Expand Down Expand Up @@ -102,39 +130,31 @@ public static IEnumerable<T> FillBackward<T>(this IEnumerable<T> source, Func<T,
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
if (fillSelector == null) throw new ArgumentNullException(nameof(fillSelector));

return FillBackwardImpl(source, predicate, fillSelector);
}

static IEnumerable<T> FillBackwardImpl<T>(IEnumerable<T> source, Func<T, bool> predicate, Func<T, T, T> fillSelector)
{
List<T> blanks = null;

foreach (var item in source)
return _(); IEnumerable<T> _()
{
var isBlank = predicate(item);
if (isBlank)
{
(blanks ??= new List<T>()).Add(item);
}
else
var blanks = new List<T>();

foreach (var item in source)
{
if (blanks != null)
// Check if the item is ‘missing’
if (predicate(item))
{
blanks.Add(item);
}
else
{
foreach (var blank in blanks)
if (blanks.Count > 0)
{
yield return fillSelector != null
? fillSelector(blank, item)
: item;
foreach (var blank in blanks)
yield return fillSelector(blank, item);

blanks.Clear();
}

blanks.Clear();
yield return item;
}
yield return item;
}
}

if (blanks?.Count > 0)
{
foreach (var blank in blanks)
yield return blank;
}
Expand Down