I created a SlidingWindow operator for reactive extensions because I want to easily monitor things like rolling averages, etc. As a simple example, I want to s
The sliding window implementations here was not adequate for my idea of a sliding window. The closest was using Buffer(N, 1) but is a problem since it waits for the first N items before it emits the first result then slides beyond the end of the sequence. I want up to N items emitted at a time.
I ended up with this implementation:
public static IObservable> SlidingWindow(this IObservable obs, int windowSize) =>
Observable.Create>(observer =>
{
var buffer = new CircularBuffer(windowSize);
return obs.Subscribe(
value =>
{
buffer.Add(value);
observer.OnNext(buffer.ToList());
},
ex => observer.OnError(ex),
() => observer.OnCompleted()
);
});
I was initially using a queue for the buffer but wanted to use something a bit more lightweight.
public class CircularBuffer : IReadOnlyList
{
private readonly T[] buffer;
private int offset;
private int count;
public CircularBuffer(int bufferSize) => this.buffer = new T[bufferSize];
public int Capacity => buffer.Length;
public int Count => count;
public T this[int index] => index < 0 || index >= count
? throw new ArgumentOutOfRangeException(nameof(index))
: buffer[(offset + index) % buffer.Length];
public void Add(T value)
{
buffer[(offset + count) % buffer.Length] = value;
if (count < buffer.Length) count++;
else offset = (offset + 1) % buffer.Length;
}
public IEnumerator GetEnumerator()
{
for (var i = 0; i < count; i++)
yield return buffer[(offset + i) % buffer.Length];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
It would yield sequences for Observable.Range(0, 10).SlidingWindow(3):
0,1,2,3,4,5,6,7,8,9
[0]
[0,1]
[0,1,2]
[1,2,3]
[2,3,4]
[3,4,5]
[4,5,6]
[5,6,7]
[6,7,8]
[7,8,9]