Trouble Implementing a Sliding Window in Rx

后端 未结 4 821
温柔的废话
温柔的废话 2020-12-15 09:38

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

相关标签:
4条回答
  • 2020-12-15 10:02

    Just source.Window(count, 1) - or source.Buffer(count, 1) It be a window/buffer of "count" items, sliding by one.

    0 讨论(0)
  • 2020-12-15 10:09

    Using your original test, with an argument of 3 for count, this gives the desired results:

    public static IObservable<IList<T>> SlidingWindow<T>(
        this IObservable<T> source, int count)
    {
        return source.Buffer(count, 1)
                     .Where(list => list.Count == count);
    }
    

    Testing like this:

    var source = Observable.Range(1, 5);
    var query = source.SlidingWindow(3);
    using (query.Subscribe(i => Console.WriteLine(string.Join(",", i))))
    {
    
    }
    

    Output:

    1,2,3
    2,3,4
    3,4,5
    
    0 讨论(0)
  • 2020-12-15 10:20

    Try this instead - I'd have to sit and have a think about it's relative performance, but it's at least likely as good, and way easier to read:

    public static IObservable<IList<T>> SlidingWindow<T>(
           this IObservable<T> src, 
           int windowSize)
    {
        var feed = src.Publish().RefCount();    
        // (skip 0) + (skip 1) + (skip 2) + ... + (skip nth) => return as list  
        return Observable.Zip(
           Enumerable.Range(0, windowSize)
               .Select(skip => feed.Skip(skip))
               .ToArray());
    }
    

    Test rig:

    var source = Observable.Range(0, 10);
    var query = source.SlidingWindow(3);
    using(query.Subscribe(Console.WriteLine))
    {               
        Console.ReadLine();
    }
    

    Output:

    ListOf(0,1,2)
    ListOf(1,2,3)
    ListOf(2,3,4)
    ListOf(3,4,5)
    ListOf(4,5,6)
    ...
    

    EDIT: As an aside, I find myself compulsively .Publish().RefCount()ing ever since being burned once by not doing it...I don't think it's strictly required here, tho.

    EDIT for yzorg:

    If you augment the method like so, you'll see the runtime behavior more clearly:

    public static IObservable<IList<T>> SlidingWindow<T>(
        this IObservable<T> src, 
        int windowSize)
    {
        var feed = src.Publish().RefCount();    
        // (skip 0) + (skip 1) + (skip 2) + ... + (skip nth) => return as list  
        return Observable.Zip(
        Enumerable.Range(0, windowSize)
            .Select(skip => 
            {
                Console.WriteLine("Skipping {0} els", skip);
                return feed.Skip(skip);
            })
            .ToArray());
    }
    
    0 讨论(0)
  • 2020-12-15 10:20

    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<IList<T>> SlidingWindow<T>(this IObservable<T> obs, int windowSize) =>
        Observable.Create<IList<T>>(observer =>
        {
            var buffer = new CircularBuffer<T>(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<T> : IReadOnlyList<T>
    {
        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<T> 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]
    
    0 讨论(0)
提交回复
热议问题