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]