给出一个课程:
class Foo { DateTime Timestamp {get; set;} }
...和IObservable<Foo>
,保证单调增加 Timestamp
,如何根据IObservable<IList<Foo>>
生成Timestamp
数据块。 1}} S'
即。每个IList<Foo>
应该有五秒钟的事件,或者其他什么。我知道我可以Buffer
使用TimeSpan
超载,但我需要花时间从事件本身,而不是挂钟。 (除非有一种聪明的方式在IScheduler
提供IObservable
本身作为.Now
的来源?)
如果我尝试使用Observable.Buffer(this IObservable<Foo> source, IObservable<Foo> bufferBoundaries)
重载,请执行以下操作:
IObservable<Foo> foos = //...;
var pub = foos.Publish();
var windows = pub.Select(x => new DateTime(
x.Ticks - x.Ticks % TimeSpan.FromSeconds(5).Ticks)).DistinctUntilChanged();
pub.Buffer(windows).Subscribe(x => t.Dump())); // linqpad
pub.Connect();
...然后IList
个实例包含导致窗口关闭的项目,但我真的希望这个项目进入下一个窗口/缓冲区。
E.g。使用时间戳[0, 1, 10, 11, 15]
,您将获得[[0], [1, 10], [11, 15]]
而不是[[0, 1], [10, 11], [15]]
答案 0 :(得分:6)
这是一个想法。组密钥条件是&#34;窗口号&#34;我使用GroupByUntil
。这为您提供了示例中所需的输出(我已经使用了一个int流,就像那个例子一样 - 但你可以用你需要的任何东西替换你的窗口编号)。
public class Tests : ReactiveTest
{
public void Test()
{
var scheduler = new TestScheduler();
var xs = scheduler.CreateHotObservable<int>(
OnNext(0, 0),
OnNext(1, 1),
OnNext(10, 10),
OnNext(11, 11),
OnNext(15, 15),
OnCompleted(16, 0));
xs.Publish(ps => // (1)
ps.GroupByUntil(
p => p / 5, // (2)
grp => ps.Where(p => p / 5 != grp.Key)) // (3)
.SelectMany(x => x.ToList())) // (4)
.Subscribe(Console.WriteLine);
scheduler.Start();
}
}
Observable.Timer+Select
合并,该Test()
输出您的术语的空/默认实例以更早地终止该流。如果你包含nuget包rx-testing,这个例子将很好地在LINQPad中运行。新建一个测试实例,然后运行{{1}}方法。
答案 1 :(得分:2)
Window
是Buffer
的概括,而GroupJoin
是Window
(和Join
)的概括。当您撰写Window
或Buffer
查询时,您发现通知被错误地包含在窗口/列表的边缘或从窗口/列表的边缘排除,然后根据GroupJoin
重新定义您的查询控制边缘通知到达的位置。
请注意,为了使关闭通知可用于新打开的窗口,您必须将边界定义为这些通知的窗口(窗口化数据,而不是边界数据)。在您的情况下,您不能使用DateTime值序列作为边界,您必须使用一系列Foo对象。为此,我已使用Select
- &gt; DistinctUntilChanged
- &gt; Scan
查询替换了您的Where
- &gt; Select
查询。
var batches = foos.Publish(publishedFoos => publishedFoos
.Scan(
new { foo = (Foo)null, last = DateTime.MinValue, take = true },
(acc, foo) =>
{
var boundary = foo.Timestamp - acc.last >= TimeSpan.FromSeconds(5);
return new
{
foo,
last = boundary ? foo.Timestamp : acc.last,
take = boundary
};
})
.Where(a => a.take)
.Select(a => a.foo)
.Publish(boundaries => boundaries
.Skip(1)
.StartWith((Foo)null)
.GroupJoin(
publishedFoos,
foo => foo == null ? boundaries.Skip(1) : boundaries,
_ => Observable.Empty<Unit>(),
(foo, window) => (foo == null ? window : window.StartWith(foo)).ToList())))
.Merge()
.Replay(lists => lists.SkipLast(1)
.Select(list => list.Take(list.Count - 1))
.Concat(lists),
bufferSize: 1);
只有当您希望序列最终结束并且您不关心不丢弃最后一个通知时,才需要最后的Replay
查询;否则,您可以简单地将window.StartWith(foo)
修改为window.StartWith(foo).SkipLast(1)
以获得相同的基本结果,但最后一个缓冲区的最后通知将会丢失。
答案 2 :(得分:2)
我认为詹姆斯世界的答案更整洁/更具可读性,但对于后人,我发现了另一种使用Buffer()
的方法:
IObservable<Foo> foos = //...;
var pub = foos.Publish();
var windows = pub.Select(x => new DateTime(
x.Ticks - x.Ticks % TimeSpan.FromSeconds(5).Ticks))
.DistinctUntilChanged().Publish.RefCount();
pub.Buffer(windows, x => windows).Subscribe(x => t.Dump()));
pub.Connect();
凭借10米赛事,詹姆斯的进近速度超过2.5倍(我的机器上20秒对56秒)。