C# 在Rx中实现滑动窗口时出现问题

C# 在Rx中实现滑动窗口时出现问题,c#,system.reactive,rx.net,C#,System.reactive,Rx.net,我为被动扩展创建了一个滑动窗口操作符,因为我想轻松监控滚动平均值等。举个简单的例子,我想订阅鼠标事件,但每次有事件时,我都想接收最后三个(而不是等待每三个事件接收最后三个)。这就是为什么我发现的窗口过载似乎没有给我开箱即用的东西 这就是我想到的。鉴于其频繁的列表操作,我担心它可能不是最有效的解决方案: public static IObservable<List<T>> SlidingWindow<T>(this IObservable<T> se

我为被动扩展创建了一个
滑动窗口
操作符,因为我想轻松监控滚动平均值等。举个简单的例子,我想订阅鼠标事件,但每次有事件时,我都想接收最后三个(而不是等待每三个事件接收最后三个)。这就是为什么我发现的窗口过载似乎没有给我开箱即用的东西

这就是我想到的。鉴于其频繁的列表操作,我担心它可能不是最有效的解决方案:

public static IObservable<List<T>> SlidingWindow<T>(this IObservable<T> seq, int length)
{
    var seed = new List<T>();

    Func<List<T>, T, List<T>> accumulator = (list, arg2) =>
    {
        list.Add(arg2);

        if (list.Count > length)
            list.RemoveRange(0, (list.Count - length));

        return list;
    };

    return seq.Scan(seed, accumulator)
                .Where(list => list.Count == length);
}
然而,令我大吃一惊的是,没有收到预期的结果

1,2,3
2,3,4
3,4,5
我收到结果

2,3,4
3,4,5
3,4,5

任何见解都将不胜感激

试试这个——我不得不坐下来思考一下它的相对性能,但它至少也一样好,而且更容易阅读:

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());
}
输出:

ListOf(0,1,2)
ListOf(1,2,3)
ListOf(2,3,4)
ListOf(3,4,5)
ListOf(4,5,6)
...
1,2,3
2,3,4
3,4,5
编辑:顺便说一句,我发现自己自从被烧过一次以后,就因为不这样做而被迫
.Publish().RefCount()
自此……我认为这不是严格要求的,tho

为yzorg编辑:

如果像这样扩展该方法,您将更清楚地看到运行时行为:

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());
}
publicstaticiobservable滑动窗口(
这是可观测的src,
int(窗口大小)
{
var feed=src.Publish().RefCount();
//(跳过0)+(跳过1)+(跳过2)+……+(跳过第n个)=>返回为列表
返回可观察的.Zip(
可枚举范围(0,窗口大小)
.选择(跳过=>
{
WriteLine(“跳过{0}个字符”,跳过);
返回馈送。跳过(跳过);
})
.ToArray());
}

使用原始测试,参数为3表示计数,这将给出所需的结果:

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

ListOf(0,1,2)
ListOf(1,2,3)
ListOf(2,3,4)
ListOf(3,4,5)
ListOf(4,5,6)
...
1,2,3
2,3,4
3,4,5
只需
source.Window(count,1)
-或
source.Buffer(count,1)

它可能是一个“计数”项的窗口/缓冲区,按1滑动。

这里的滑动窗口实现不适合我的滑动窗口想法。最近的一个是使用
缓冲区(N,1)
,但这是一个问题,因为它在发出第一个结果之前等待前N个项目,然后滑到序列末尾之外。我希望一次最多发射N个项目

我最终实现了以下功能:

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()
        );
    });

@blaster没问题——事实上,谢谢你“让”我写出来,因为我自己在回答这个问题后已经用过好几次了我认为这不好。.Publish()、.Range(0,x)和.Skip()--当它们组合在一起时,性能看起来很差,特别是在^2上,因为Skip将一次又一次地迭代整个流。例如,需要迭代30000个整数才能得到(100001000110002)。因此,您实际上没有在内存中保留源流的滑动缓冲区,您必须将整个源流(从时间开始)保留在内存中,这是我认为我们要避免的。此外,这是为了“合理”大小窗口…我不会将此用于10k窗口。James World或Luke的应该是公认的答案。我的特定用例是,限制linqpad中任务的日志输出,以输出最多N个项,以防止达到输出窗口的限制
logs.SlidingWindow(1000).DumpLatest()
我知道这是一个很老的答案,但它是
。在哪里(list=>list.Count==Count)
需要?我尝试不使用它,只是创建了一个
缓冲区(count,1)
,它似乎也能工作。它将失败,因为序列中的最后一个
count-1
事件将以少于
count
项的方式输出,OP在窗口中特别要求
count
项。用我答案中的测试用例试试看会发生什么。i、 你会得到另外两个包含“4,5”和“5”的项目。你是对的。在我之前的测试中,我使用了一个ISubject而不是一个IObservable:查看文档,ISubject扩展了IObservable,因此在这种情况下可能有一个不同的策略,因此最后的“4、5”和“5”不会返回,即使
中缺少
。我是反应型的新手,我还需要学习
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()
        );
    });
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();
}
 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]