Behavioral.Iterator.WindowSequence
WindowSequence.Windows(...) is a fluent, allocation-light helper that produces sliding or striding windows over any IEnumerable<T>.
It demonstrates how you can extend the classic Iterator pattern with richer semantics (window size, stride, partial trailing
windows, reusable buffers) while still exposing a standard, lazy, LINQ-friendly API.
TL;DR
using PatternKit.Behavioral.Iterator;
var windows = Enumerable.Range(1, 7)
.Windows(size: 3, stride: 1) // slide 1 each time
.Select(w => string.Join(',', w.ToArray()));
// -> ["1,2,3", "2,3,4", "3,4,5", "4,5,6", "5,6,7"]
Stride 2 (skip elements between starts):
var stepped = Enumerable.Range(1, 9)
.Windows(size: 4, stride: 2)
.Select(w => string.Join('-', w.ToArray()));
// -> ["1-2-3-4", "3-4-5-6", "5-6-7-8"]
Include trailing partial window:
var partials = new[]{1,2,3,4,5}
.Windows(size: 3, stride: 3, includePartial: true)
.Select(w => $"[{string.Join(',', w.ToArray())}] (partial={w.IsPartial})");
// -> ["[1,2,3] (partial=False)", "[4,5] (partial=True)"]
Reuse a buffer (zero alloc per full window, but you MUST copy if you retain data):
var reused = Enumerable.Range(1, 6)
.Windows(size: 3, reuseBuffer: true)
.Select(w => w.ToArray()) // force snapshot copy each time
.ToList();
// windows: [1,2,3], [2,3,4], [3,4,5], [4,5,6]
API Shape
public static class WindowSequence
{
public static IEnumerable<Window<T>> Windows<T>(
this IEnumerable<T> source,
int size,
int stride = 1,
bool includePartial = false,
bool reuseBuffer = false);
public readonly struct Window<T>
{
public int Count { get; }
public bool IsPartial { get; }
public bool IsBufferReused { get; }
public T this[int index] { get; }
public T[] ToArray(); // always copies
public IEnumerator<T> GetEnumerator();
}
}
Parameters
size– required window length (> 0).stride– elements to advance between successive window starts (default 1).includePartial– include trailing window withCount < size.reuseBuffer– reuse a single backing array for full windows (partial windows still copy). CallToArray()to snapshot.
Semantics & Guarantees
| Aspect | Behavior |
|---|---|
| Enumeration | Single pass over source; deferred execution. |
| Overlap | Controlled by stride (1 = full overlap sliding). |
| Partial | Disabled by default; enable via includePartial. |
| Reuse | When reuseBuffer=true, full windows share the same array (treat returned Window as ephemeral unless copied). |
| Safety | ToArray() always returns an independent copy. |
Use Cases
- Batch / micro-batch analytics (moving averages, rolling sums)
- Stream framing (fixed length records with overlap)
- Feature extraction windows (ML preprocessing)
- Temporal rule evaluation (N previous events)
Example: Rolling Average
double RollingAverage(IEnumerable<int> src, int windowSize)
=> src.Windows(windowSize)
.Select(w => w.ToArray().Average())
.LastOrDefault();
Example: Find First Increasing Triple
var triple = nums.Windows(size:3)
.Select(w => w.ToArray())
.FirstOrDefault(arr => arr[0] < arr[1] && arr[1] < arr[2]);
Performance Notes
- Uses a
Queue<T>internally for clarity – O(size) per full window snapshot when copying. - For extremely hot paths, a ring buffer variant would reduce copy cost; this implementation prioritizes readability.
- Set
reuseBuffer:trueto avoid per-window allocations (copy yourself if persistent storage is required).
Gotchas
| Gotcha | Explanation |
|---|---|
| Buffer reuse surprises | Mutating or retaining reused buffer contents without copying will show later window values. Always call ToArray() if you persist. |
| Large stride + partial disabled | You may silently drop tail elements—enable includePartial if you need them. |
| size or stride <= 0 | Immediate ArgumentOutOfRangeException. |
See Also
- ReplayableSequence – multi-cursor, lookahead iteration.
- LINQ standard operators for reference semantics.