diff --git a/MoreLinq.Test/FillBackwardTest.cs b/MoreLinq.Test/FillBackwardTest.cs index c6e713412..63b840117 100644 --- a/MoreLinq.Test/FillBackwardTest.cs +++ b/MoreLinq.Test/FillBackwardTest.cs @@ -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 Seq(params T[] values) => values; + + [TestCase(TestName = "FillBackward is lazy")] public void FillBackwardIsLazy() { - new BreakingSequence().FillBackward(); + var source = new BreakingSequence(); + source.FillBackward(); } - [Test] - public void FillBackward() + public static readonly IEnumerable FillBackwardTestData = + from e in new[] + { + new + { + Name = "FillBackward: Input sequence is empty", + Source = Enumerable.Empty(), + Expected = Enumerable.Empty() + }, + new + { + Name = "FillBackward: Input sequence has no null values", + Source = Seq(1, 2, 3), + Expected = Seq(1, 2, 3) + }, + new + { + Name = "FillBackward: Input sequence has only nulls but last value", + Source = Seq(null, null, 0), + Expected = Seq(0, 0, 0) + }, + new + { + Name = "FillBackward: Input sequence ends with nulls", + Source = Seq(null, null, 0, null, null), + Expected = Seq(0, 0, 0, null, null) + }, + new + { + Name = "FillBackward: Input sequence with distributed nulls", + Source = Seq(0, null, null, 1, 2, null, null, 3, 4, null, null), + Expected = Seq(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 FillBackward(IEnumerable 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(); + var predicate = BreakingFunc.Of(); + source.FillBackward(predicate); + } - Assert.That(result, Is.EqualTo(new[] + public static readonly IEnumerable 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(), + Expected = Enumerable.Empty() + }, + 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 FillBackwardWithPredicate(IEnumerable 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(); + var predicate = BreakingFunc.Of(); + var fillSelector = BreakingFunc.Of(); + source.FillBackward(predicate, fillSelector); + } + + public static readonly IEnumerable + FillBackwardWithPredicateAndFillSelectorTestData = + from e in new[] + { + new + { + Name = "FillBackward, with predicate and fill selector: Input sequence is empty", + Source = Enumerable.Empty(), + Expected = Enumerable.Empty() + }, + 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 FillBackwardWithPredicateAndFillSelector(IEnumerable source) + { + static bool Predicate(int v) => v < 0; + static int FillSelector(int missing, int nonMissing) => nonMissing - 10 * missing; + return source.AsTestingSequence().FillBackward(Predicate, FillSelector); } } } diff --git a/MoreLinq/FillBackward.cs b/MoreLinq/FillBackward.cs index 1b4cc5514..8462d66aa 100644 --- a/MoreLinq/FillBackward.cs +++ b/MoreLinq/FillBackward.cs @@ -68,13 +68,41 @@ public static IEnumerable FillBackward(this IEnumerable source, Func _() + { + // We need to store the blanks in case of the source sequence ends with missing values + var blanks = new List(); + + 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; + } } /// /// 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. /// @@ -102,39 +130,31 @@ public static IEnumerable FillBackward(this IEnumerable source, Func FillBackwardImpl(IEnumerable source, Func predicate, Func fillSelector) - { - List blanks = null; - - foreach (var item in source) + return _(); IEnumerable _() { - var isBlank = predicate(item); - if (isBlank) - { - (blanks ??= new List()).Add(item); - } - else + var blanks = new List(); + + 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; }