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

Refactor Window(Left|Right) #656

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8e179ad
Add TestWindowDoNotExposeItsBuffer, WindowLeftDoNotExposeItsBuffer an…
Orace Nov 6, 2019
7cc8615
Add TestWindowOnKnownResults
Orace Nov 6, 2019
8765b1b
Renamed tests Window*DoNotExposeItsBuffer => TestWindow*ReturnIsolate…
Orace Nov 6, 2019
340858b
Fix TestWindow*ReturnIsolatedList
Orace Nov 6, 2019
e41a21d
Window* methods returned elements that are not isolated from each other.
Orace Nov 6, 2019
2ca1b7a
Add TestWindowLeftOnKnownResults and TestWindowRightOnKnownResults
Orace Nov 6, 2019
207781b
Refactor Window(Left|Right) so they use the same implementation.
Orace Nov 6, 2019
f2864ce
Removed useless internal method in Window implementation
Orace Nov 6, 2019
7b4653a
Remove unused using directives.
Orace Nov 6, 2019
6c0de36
Added some test for Window(Left|Right):
Orace Nov 6, 2019
9fd0df4
Merge pull request #1 from Orace/FixWindow
Orace Nov 6, 2019
f22964f
Merge commit '2ca1b7a' into FixWindow
Orace Nov 6, 2019
a4e05ce
Merge branch 'FixWindow' into ImproveWindow
Orace Nov 6, 2019
66c29a3
Rename ModifyWindowBeforeMoveNextDoNotAffectPrevWindow to ModifyWind…
Orace Nov 6, 2019
d221cea
Rename ModifyWindowBeforeMoveNextDoNotAffectPrevWindow to ModifyWind…
Orace Nov 6, 2019
be6af97
Merge branch 'FixWindow' into ImproveWindow
Orace Nov 6, 2019
5598a8d
Simplify Window implementation
Orace Nov 6, 2019
cc21cc4
Clean-up tests
Orace Nov 8, 2019
625758f
Merge branch 'FixWindow' into ImproveWindow
Orace Nov 8, 2019
3179659
Niptick improvement in Window
Orace Nov 8, 2019
0c9bd2b
Update MoreLinq/Window.cs
Orace Nov 8, 2019
1e46804
Update MoreLinq/Window.cs
Orace Nov 8, 2019
a265e39
Merge branch 'master' into FixWindow
Orace Nov 15, 2019
f33d0fe
Merge remote-tracking branch 'origin/FixWindow' into ImproveWindow
Orace Nov 15, 2019
99f3666
Merge branch 'mlMaster' into ImproveWindow
Orace Nov 20, 2019
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
21 changes: 21 additions & 0 deletions MoreLinq.Test/WindowLeftTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace MoreLinq.Test
{
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework.Interfaces;

[TestFixture]
public class WindowLeftTest
Expand Down Expand Up @@ -138,5 +139,25 @@ public void WindowLeftWithWindowSizeSmallerThanSequence()
reader.ReadEnd();
}
}

static IEnumerable<T> Seq<T>(params T[] values) => values;

public static readonly IEnumerable<ITestCaseData> 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<IEnumerable<int>> TestWindowLeftOnKnownResults(IEnumerable<int> sequence, int sizes)
{
using var testingSequence = sequence.AsTestingSequence();
var r = testingSequence.WindowLeft(sizes).ToList();
return r;
}
}
}
21 changes: 21 additions & 0 deletions MoreLinq.Test/WindowRightTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace MoreLinq.Test
{
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework.Interfaces;

[TestFixture]
public class WindowRightTest
Expand Down Expand Up @@ -138,5 +139,25 @@ public void WindowRightWithWindowSizeSmallerThanSequence()
reader.ReadEnd();
}
}

static IEnumerable<T> Seq<T>(params T[] values) => values;

public static readonly IEnumerable<ITestCaseData> 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<IEnumerable<int>> TestWindowRightOnKnownResults(IEnumerable<int> sequence, int sizes)
{
using var testingSequence = sequence.AsTestingSequence();
var r = testingSequence.WindowRight(sizes).ToList();
return r;
}
}
}
21 changes: 21 additions & 0 deletions MoreLinq.Test/WindowTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

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

/// <summary>
/// Verify the behavior of the Window operator
Expand Down Expand Up @@ -176,5 +178,24 @@ public void TestWindowWindowsImmutability()
reader.ReadEnd();
}
}

static IEnumerable<T> Seq<T>(params T[] values) => values;

public static readonly IEnumerable<ITestCaseData> 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<IEnumerable<int>> TestWindowOnKnownResults(IEnumerable<int> sequence, int sizes)
{
using var testingSequence = sequence.AsTestingSequence();
return testingSequence.Window(sizes).ToList();
}
}
}
130 changes: 102 additions & 28 deletions MoreLinq/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,34 +39,7 @@ public static IEnumerable<IList<TSource>> Window<TSource>(this IEnumerable<TSour
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));

return _(); IEnumerable<IList<TSource>> _()
{
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);
}

/// <summary>
Expand All @@ -84,5 +57,106 @@ public static IEnumerable<IList<TSource>> Window<TSource>(this IEnumerable<TSour
[Obsolete("Use " + nameof(Window) + " instead.")]
public static IEnumerable<IEnumerable<TSource>> Windowed<TSource>(this IEnumerable<TSource> source, int size) =>
source.Window(size);

private static IEnumerable<IList<TSource>> Window<TSource>(IEnumerable<TSource> 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;
}
}

Orace marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}
}
}
}
24 changes: 1 addition & 23 deletions MoreLinq/WindowLeft.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ namespace MoreLinq
{
using System;
using System.Collections.Generic;
using System.Linq;

public static partial class MoreEnumerable
{
Expand Down Expand Up @@ -63,28 +62,7 @@ public static IEnumerable<IList<TSource>> WindowLeft<TSource>(this IEnumerable<T
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));

return _(); IEnumerable<IList<TSource>> _()
{
var window = new List<TSource>();
foreach (var item in source)
{
window.Add(item);
if (window.Count < size)
continue;

// prepare next window before exposing data
var nextWindow = new List<TSource>(window.Skip(1));
yield return window;
window = nextWindow;
}
while (window.Count > 0)
{
// prepare next window before exposing data
var nextWindow = new List<TSource>(window.Skip(1));
yield return window;
window = nextWindow;
}
}
return Window(source, size, false, true);
}
}
}
30 changes: 1 addition & 29 deletions MoreLinq/WindowRight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ namespace MoreLinq
{
using System;
using System.Collections.Generic;
using System.Linq;

public static partial class MoreEnumerable
{
Expand Down Expand Up @@ -63,34 +62,7 @@ public static IEnumerable<IList<TSource>> WindowRight<TSource>(this IEnumerable<
if (source == null) throw new ArgumentNullException(nameof(source));
if (size < 0) throw new ArgumentOutOfRangeException(nameof(size));

return source.WindowRightWhile((_, i) => i < size);
}

/// <summary>
/// Creates a right-aligned sliding window over the source sequence
/// with a predicate function determining the window range.
/// </summary>

static IEnumerable<IList<TSource>> WindowRightWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource, int, bool> predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));

return _(); IEnumerable<IList<TSource>> _()
{
var window = new List<TSource>();
foreach (var item in source)
{
window.Add(item);

// prepare next window before exposing data
var nextWindow = new List<TSource>(predicate(item, window.Count) ? window : window.Skip(1));
yield return window;
window = nextWindow;
}
}
return Window(source, size, true, false);
}
}
}