diff --git a/MoreLinq.Test/WindowLeftTest.cs b/MoreLinq.Test/WindowLeftTest.cs index c63dc071c..765cbd1e4 100644 --- a/MoreLinq.Test/WindowLeftTest.cs +++ b/MoreLinq.Test/WindowLeftTest.cs @@ -19,6 +19,7 @@ namespace MoreLinq.Test { using System.Collections.Generic; using NUnit.Framework; + using NUnit.Framework.Interfaces; [TestFixture] public class WindowLeftTest @@ -138,5 +139,25 @@ public void WindowLeftWithWindowSizeSmallerThanSequence() reader.ReadEnd(); } } + + static IEnumerable Seq(params T[] values) => values; + + public static readonly IEnumerable TestData = + from e in new[] + { + new {Source = Enumerable.Range(0, 4), Size = 1, Result = new[] {Seq(0), Seq(1), Seq(2), Seq(3)}}, + new {Source = Enumerable.Range(0, 4), Size = 2, Result = new[] {Seq(0, 1), Seq(1, 2), Seq(2, 3), Seq(3)}}, + new {Source = Enumerable.Range(0, 4), Size = 3, Result = new[] {Seq(0, 1, 2), Seq(1, 2, 3), Seq(2, 3), Seq(3)}}, + new {Source = Enumerable.Range(0, 4), Size = 4, Result = new[] {Seq(0, 1, 2, 3), Seq(1, 2, 3), Seq(2, 3), Seq(3)}} + } + select new TestCaseData(e.Source, e.Size).Returns(e.Result); + + [Test, TestCaseSource(nameof(TestData))] + public IEnumerable> TestWindowLeftOnKnownResults(IEnumerable sequence, int sizes) + { + using var testingSequence = sequence.AsTestingSequence(); + var r = testingSequence.WindowLeft(sizes).ToList(); + return r; + } } } diff --git a/MoreLinq.Test/WindowRightTest.cs b/MoreLinq.Test/WindowRightTest.cs index d5296e307..ac3aa0c6e 100644 --- a/MoreLinq.Test/WindowRightTest.cs +++ b/MoreLinq.Test/WindowRightTest.cs @@ -19,6 +19,7 @@ namespace MoreLinq.Test { using System.Collections.Generic; using NUnit.Framework; + using NUnit.Framework.Interfaces; [TestFixture] public class WindowRightTest @@ -138,5 +139,25 @@ public void WindowRightWithWindowSizeSmallerThanSequence() reader.ReadEnd(); } } + + static IEnumerable Seq(params T[] values) => values; + + public static readonly IEnumerable TestData = + from e in new[] + { + new {Source = Enumerable.Range(0, 4), Size = 1, Result = new[] {Seq(0), Seq(1), Seq(2), Seq(3)}}, + new {Source = Enumerable.Range(0, 4), Size = 2, Result = new[] {Seq(0), Seq(0, 1), Seq(1, 2), Seq(2, 3)}}, + new {Source = Enumerable.Range(0, 4), Size = 3, Result = new[] {Seq(0), Seq(0, 1), Seq(0, 1, 2), Seq(1, 2, 3)}}, + new {Source = Enumerable.Range(0, 4), Size = 4, Result = new[] {Seq(0), Seq(0, 1), Seq(0, 1, 2), Seq(0, 1, 2, 3)}} + } + select new TestCaseData(e.Source, e.Size).Returns(e.Result); + + [Test, TestCaseSource(nameof(TestData))] + public IEnumerable> TestWindowRightOnKnownResults(IEnumerable sequence, int sizes) + { + using var testingSequence = sequence.AsTestingSequence(); + var r = testingSequence.WindowRight(sizes).ToList(); + return r; + } } } diff --git a/MoreLinq.Test/WindowTest.cs b/MoreLinq.Test/WindowTest.cs index 2fc765be9..9c7d49717 100644 --- a/MoreLinq.Test/WindowTest.cs +++ b/MoreLinq.Test/WindowTest.cs @@ -17,7 +17,9 @@ namespace MoreLinq.Test { + using System.Collections.Generic; using NUnit.Framework; + using NUnit.Framework.Interfaces; /// /// Verify the behavior of the Window operator @@ -176,5 +178,24 @@ public void TestWindowWindowsImmutability() reader.ReadEnd(); } } + + static IEnumerable Seq(params T[] values) => values; + + public static readonly IEnumerable TestData = + from e in new[] + { + new {Source = Enumerable.Range(0, 4), Size = 1, Result = new[] {Seq(0), Seq(1), Seq(2), Seq(3)}}, + new {Source = Enumerable.Range(0, 4), Size = 2, Result = new[] {Seq(0, 1), Seq(1, 2), Seq(2, 3)}}, + new {Source = Enumerable.Range(0, 4), Size = 3, Result = new[] {Seq(0, 1, 2), Seq(1, 2, 3)}}, + new {Source = Enumerable.Range(0, 4), Size = 4, Result = new[] {Seq(0, 1, 2, 3)}} + } + select new TestCaseData(e.Source, e.Size).Returns(e.Result); + + [Test, TestCaseSource(nameof(TestData))] + public IEnumerable> TestWindowOnKnownResults(IEnumerable sequence, int sizes) + { + using var testingSequence = sequence.AsTestingSequence(); + return testingSequence.Window(sizes).ToList(); + } } } diff --git a/MoreLinq/Window.cs b/MoreLinq/Window.cs index a3c5375f6..32fa9b7ae 100644 --- a/MoreLinq/Window.cs +++ b/MoreLinq/Window.cs @@ -39,34 +39,7 @@ public static IEnumerable> Window(this IEnumerable> _() - { - using var iter = source.GetEnumerator(); - - // generate the first window of items - var window = new TSource[size]; - int i; - for (i = 0; i < size && iter.MoveNext(); i++) - window[i] = iter.Current; - - if (i < size) - yield break; - - while (iter.MoveNext()) - { - // generate the next window by shifting forward by one item - // and do that before exposing the data - var newWindow = new TSource[size]; - Array.Copy(window, 1, newWindow, 0, size - 1); - newWindow[size - 1] = iter.Current; - - yield return window; - window = newWindow; - } - - // return the last window. - yield return window; - } + return Window(source, size, false, false); } /// @@ -84,5 +57,106 @@ public static IEnumerable> Window(this IEnumerable> Windowed(this IEnumerable source, int size) => source.Window(size); + + private static IEnumerable> Window(IEnumerable source, int size, + bool hasPartialBegin, bool hasPartialEnd) + { + using var iter = source.GetEnumerator(); + + var hasNext = iter.MoveNext(); + + // early break + if (!hasNext) + yield break; + + // Store the window to be yield. + // In any cases we build the next window (if any) before yielding + // Loops do not have to yield the last window they created. + TSource[] window; + + // Warm-up + if (hasPartialBegin) + { + // build first partial window; + window = new[] {iter.Current}; + hasNext = iter.MoveNext(); + + // build other partial windows + while (window.Length < size && hasNext) + { + // Prepare next window, bigger than the previous one + var nextWindow = new TSource[window.Length + 1]; + Array.Copy(window, nextWindow, window.Length); + + // window ready to ship, we forget it immediately + yield return window; + + nextWindow[nextWindow.Length - 1] = iter.Current; + hasNext = iter.MoveNext(); + window = nextWindow; + } + } + else + { + // build first window + window = new TSource[size]; + int i; + for (i = 0; i < size && hasNext; i++) + { + window[i] = iter.Current; + hasNext = iter.MoveNext(); + } + + // Ensure correct size on partial window cases + if (i != size) + { + if (hasPartialEnd) + Array.Resize(ref window, i); + else + yield break; + } + } + + // Main loop + if (window.Length == size) + { + // build windows of given size + while (hasNext) + { + // Prepare next window, same size as the previous one + var nextWindow = new TSource[size]; + Array.Copy(window, 1, nextWindow, 0, size - 1); + + // window ready to ship, we forget it immediately + yield return window; + + nextWindow[size - 1] = iter.Current; + hasNext = iter.MoveNext(); + window = nextWindow; + } + } + + // Cool down + if (hasPartialEnd) + { + // build final partial windows + while (window.Length > 1) + { + // Prepare next window, smaller than the previous one + var nextWindow = new TSource[window.Length - 1]; + Array.Copy(window, 1, nextWindow, 0, nextWindow.Length); + + // window ready to ship, we forget it immediately + yield return window; + window = nextWindow; + } + } + + // No more windows to build, we can finally yield this one + if (hasPartialBegin || hasPartialEnd || window.Length == size) + { + yield return window; + } + } } } diff --git a/MoreLinq/WindowLeft.cs b/MoreLinq/WindowLeft.cs index fdcd72803..589425cc9 100644 --- a/MoreLinq/WindowLeft.cs +++ b/MoreLinq/WindowLeft.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Linq; public static partial class MoreEnumerable { @@ -63,28 +62,7 @@ public static IEnumerable> WindowLeft(this IEnumerable> _() - { - var window = new List(); - foreach (var item in source) - { - window.Add(item); - if (window.Count < size) - continue; - - // prepare next window before exposing data - var nextWindow = new List(window.Skip(1)); - yield return window; - window = nextWindow; - } - while (window.Count > 0) - { - // prepare next window before exposing data - var nextWindow = new List(window.Skip(1)); - yield return window; - window = nextWindow; - } - } + return Window(source, size, false, true); } } } diff --git a/MoreLinq/WindowRight.cs b/MoreLinq/WindowRight.cs index 4172c3d6d..8231a97d7 100644 --- a/MoreLinq/WindowRight.cs +++ b/MoreLinq/WindowRight.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Linq; public static partial class MoreEnumerable { @@ -63,34 +62,7 @@ public static IEnumerable> WindowRight(this IEnumerable< if (source == null) throw new ArgumentNullException(nameof(source)); if (size < 0) throw new ArgumentOutOfRangeException(nameof(size)); - return source.WindowRightWhile((_, i) => i < size); - } - - /// - /// Creates a right-aligned sliding window over the source sequence - /// with a predicate function determining the window range. - /// - - static IEnumerable> WindowRightWhile( - this IEnumerable source, - Func predicate) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (predicate == null) throw new ArgumentNullException(nameof(predicate)); - - return _(); IEnumerable> _() - { - var window = new List(); - foreach (var item in source) - { - window.Add(item); - - // prepare next window before exposing data - var nextWindow = new List(predicate(item, window.Count) ? window : window.Skip(1)); - yield return window; - window = nextWindow; - } - } + return Window(source, size, true, false); } } }